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,187 @@
"""
Сервис для работы с playbook
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update, delete
from app.models.database import Playbook, PlaybookTestRun, PlaybookDeployment
from typing import Optional, List, Dict
import yaml
import logging
logger = logging.getLogger(__name__)
class PlaybookService:
"""Сервис для работы с playbook"""
@staticmethod
async def create_playbook(
db: AsyncSession,
name: str,
roles: List[str],
description: Optional[str] = None,
variables: Optional[Dict] = None,
inventory: Optional[str] = None,
created_by: Optional[str] = None
) -> Playbook:
"""Создание нового playbook"""
# Генерация YAML содержимого playbook
playbook_content = PlaybookService._generate_playbook_yaml(roles, variables)
playbook = Playbook(
name=name,
description=description,
content=playbook_content,
roles=roles,
variables=variables or {},
inventory=inventory,
created_by=created_by
)
db.add(playbook)
await db.commit()
await db.refresh(playbook)
return playbook
@staticmethod
async def get_playbook(db: AsyncSession, playbook_id: int) -> Optional[Playbook]:
"""Получение playbook по ID"""
result = await db.execute(select(Playbook).where(Playbook.id == playbook_id))
return result.scalar_one_or_none()
@staticmethod
async def get_playbook_by_name(db: AsyncSession, name: str) -> Optional[Playbook]:
"""Получение playbook по имени"""
result = await db.execute(select(Playbook).where(Playbook.name == name))
return result.scalar_one_or_none()
@staticmethod
async def list_playbooks(db: AsyncSession, status: Optional[str] = None) -> List[Playbook]:
"""Список всех playbook"""
query = select(Playbook)
if status:
query = query.where(Playbook.status == status)
result = await db.execute(query.order_by(Playbook.created_at.desc()))
return result.scalars().all()
@staticmethod
async def update_playbook(
db: AsyncSession,
playbook_id: int,
name: Optional[str] = None,
description: Optional[str] = None,
roles: Optional[List[str]] = None,
variables: Optional[Dict] = None,
inventory: Optional[str] = None,
content: Optional[str] = None,
updated_by: Optional[str] = None
) -> Optional[Playbook]:
"""Обновление playbook"""
playbook = await PlaybookService.get_playbook(db, playbook_id)
if not playbook:
return None
if name:
playbook.name = name
if description is not None:
playbook.description = description
if roles is not None:
playbook.roles = roles
# Перегенерируем content если изменились роли
playbook.content = PlaybookService._generate_playbook_yaml(roles, variables or playbook.variables)
if variables is not None:
playbook.variables = variables
# Перегенерируем content если изменились переменные
playbook.content = PlaybookService._generate_playbook_yaml(playbook.roles, variables)
if inventory is not None:
playbook.inventory = inventory
if content is not None:
playbook.content = content
if updated_by:
playbook.updated_by = updated_by
await db.commit()
await db.refresh(playbook)
return playbook
@staticmethod
async def delete_playbook(db: AsyncSession, playbook_id: int) -> bool:
"""Удаление playbook"""
playbook = await PlaybookService.get_playbook(db, playbook_id)
if not playbook:
return False
await db.delete(playbook)
await db.commit()
return True
@staticmethod
def _generate_playbook_yaml(roles: List[str], variables: Optional[Dict] = None) -> str:
"""Генерация YAML содержимого playbook"""
playbook_data = {
'name': 'Playbook',
'hosts': 'all',
'become': True,
'roles': roles
}
if variables:
playbook_data['vars'] = variables
return yaml.dump([playbook_data], default_flow_style=False, allow_unicode=True)
@staticmethod
async def save_test_run(
db: AsyncSession,
playbook_id: int,
preset_name: Optional[str],
status: str,
user: Optional[str] = None,
output: Optional[str] = None,
error: Optional[str] = None,
returncode: Optional[int] = None
) -> PlaybookTestRun:
"""Сохранение результата тестирования playbook"""
test_run = PlaybookTestRun(
playbook_id=playbook_id,
preset_name=preset_name,
status=status,
output=output,
error=error,
returncode=returncode,
user=user
)
db.add(test_run)
await db.commit()
await db.refresh(test_run)
return test_run
@staticmethod
async def save_deployment(
db: AsyncSession,
playbook_id: int,
inventory: Optional[str],
hosts: Optional[List[str]],
status: str,
user: Optional[str] = None,
output: Optional[str] = None,
error: Optional[str] = None,
returncode: Optional[int] = None
) -> PlaybookDeployment:
"""Сохранение результата деплоя playbook"""
deployment = PlaybookDeployment(
playbook_id=playbook_id,
inventory=inventory,
hosts=hosts or [],
status=status,
output=output,
error=error,
returncode=returncode,
user=user
)
db.add(deployment)
await db.commit()
await db.refresh(deployment)
return deployment