""" Сервис для работы с 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