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,168 @@
"""
Сервис для работы с Dockerfile
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.models.database import Dockerfile
from typing import Optional, List
from pathlib import Path
import logging
logger = logging.getLogger(__name__)
class DockerfileService:
"""Сервис для работы с Dockerfile"""
@staticmethod
async def get_dockerfile(db: AsyncSession, dockerfile_id: int) -> Optional[Dockerfile]:
"""Получение Dockerfile по ID"""
result = await db.execute(select(Dockerfile).where(Dockerfile.id == dockerfile_id))
return result.scalar_one_or_none()
@staticmethod
async def get_dockerfile_by_name(db: AsyncSession, name: str) -> Optional[Dockerfile]:
"""Получение Dockerfile по имени"""
result = await db.execute(select(Dockerfile).where(Dockerfile.name == name))
return result.scalar_one_or_none()
@staticmethod
async def list_dockerfiles(db: AsyncSession, status: Optional[str] = None) -> List[Dockerfile]:
"""Список всех Dockerfile"""
query = select(Dockerfile)
if status:
query = query.where(Dockerfile.status == status)
result = await db.execute(query.order_by(Dockerfile.name))
return result.scalars().all()
@staticmethod
async def create_dockerfile(
db: AsyncSession,
name: str,
content: str,
description: Optional[str] = None,
base_image: Optional[str] = None,
tags: Optional[List[str]] = None,
platforms: Optional[List[str]] = None,
created_by: Optional[str] = None
) -> Dockerfile:
"""Создание нового Dockerfile"""
# Платформы по умолчанию: linux/amd64 (x86_64), linux/386 (x86) и linux/arm64 (macOS M1)
if platforms is None:
platforms = ["linux/amd64", "linux/386", "linux/arm64"]
dockerfile = Dockerfile(
name=name,
description=description,
content=content,
base_image=base_image,
tags=tags or [],
platforms=platforms,
created_by=created_by
)
db.add(dockerfile)
await db.commit()
await db.refresh(dockerfile)
return dockerfile
@staticmethod
async def update_dockerfile(
db: AsyncSession,
dockerfile_id: int,
name: Optional[str] = None,
description: Optional[str] = None,
content: Optional[str] = None,
base_image: Optional[str] = None,
tags: Optional[List[str]] = None,
platforms: Optional[List[str]] = None,
updated_by: Optional[str] = None
) -> Optional[Dockerfile]:
"""Обновление Dockerfile"""
dockerfile = await DockerfileService.get_dockerfile(db, dockerfile_id)
if not dockerfile:
return None
if name:
dockerfile.name = name
if description is not None:
dockerfile.description = description
if content is not None:
dockerfile.content = content
if base_image is not None:
dockerfile.base_image = base_image
if tags is not None:
dockerfile.tags = tags
if platforms is not None:
dockerfile.platforms = platforms
if updated_by:
dockerfile.updated_by = updated_by
await db.commit()
await db.refresh(dockerfile)
return dockerfile
@staticmethod
async def delete_dockerfile(db: AsyncSession, dockerfile_id: int) -> bool:
"""Удаление Dockerfile"""
dockerfile = await DockerfileService.get_dockerfile(db, dockerfile_id)
if not dockerfile:
return False
await db.delete(dockerfile)
await db.commit()
return True
@staticmethod
async def load_from_filesystem(
db: AsyncSession,
project_root: Path,
created_by: Optional[str] = None
) -> List[Dockerfile]:
"""Загрузка Dockerfile из файловой системы в БД"""
# Dockerfiles теперь находятся в alembic/dockerfiles
alembic_dir = project_root / "app" / "alembic"
dockerfiles_dir = alembic_dir / "dockerfiles"
loaded = []
if not dockerfiles_dir.exists():
logger.warning(f"Dockerfiles directory not found: {dockerfiles_dir}")
return loaded
for dockerfile_path in dockerfiles_dir.rglob("Dockerfile*"):
if dockerfile_path.is_file():
# Имя из пути (например, ubuntu22/Dockerfile -> ubuntu22)
relative_path = dockerfile_path.relative_to(dockerfiles_dir)
name = str(relative_path.parent) if relative_path.parent != Path('.') else relative_path.stem
# Проверяем, существует ли уже в БД
existing = await DockerfileService.get_dockerfile_by_name(db, name)
if existing:
continue
content = dockerfile_path.read_text(encoding='utf-8')
# Определяем базовый образ из содержимого
base_image = None
for line in content.split('\n'):
if line.strip().startswith('FROM'):
base_image = line.strip().replace('FROM', '').strip().split()[0]
break
# Платформы по умолчанию: linux/amd64 (x86_64), linux/386 (x86) и linux/arm64 (macOS M1)
default_platforms = ["linux/amd64", "linux/386", "linux/arm64"]
dockerfile = await DockerfileService.create_dockerfile(
db=db,
name=name,
content=content,
base_image=base_image,
platforms=default_platforms,
created_by=created_by
)
loaded.append(dockerfile)
logger.info(f"Loaded Dockerfile: {name}")
return loaded