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