feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile
- Добавлена колонка 'Тип' во все таблицы истории сборок - Для push операций отображается registry вместо платформ - Сохранение пользователя при создании push лога - Исправлена ошибка с logger в push_docker_image endpoint - Улучшено отображение истории сборок с визуальными индикаторами
This commit is contained in:
233
app/services/import_service.py
Normal file
233
app/services/import_service.py
Normal file
@@ -0,0 +1,233 @@
|
||||
"""
|
||||
Сервис для импорта ролей из репозиториев и Ansible Galaxy
|
||||
Автор: Сергей Антропов
|
||||
Сайт: https://devops.org.ru
|
||||
"""
|
||||
|
||||
import shutil
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional
|
||||
import yaml
|
||||
from git import Repo
|
||||
from git.exc import GitCommandError
|
||||
from app.core.config import settings
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImportService:
|
||||
"""Сервис для импорта ролей"""
|
||||
|
||||
def __init__(self):
|
||||
self.project_root = settings.PROJECT_ROOT
|
||||
self.roles_dir = self.project_root / "roles"
|
||||
|
||||
async def import_from_git(
|
||||
self,
|
||||
repo_url: str,
|
||||
role_name: Optional[str] = None,
|
||||
branch: str = "main",
|
||||
subdirectory: Optional[str] = None
|
||||
) -> Dict:
|
||||
"""
|
||||
Импорт роли из Git репозитория
|
||||
|
||||
Args:
|
||||
repo_url: URL Git репозитория
|
||||
role_name: Имя роли (если не указано, берется из имени репозитория)
|
||||
branch: Ветка для клонирования
|
||||
subdirectory: Поддиректория в репозитории (если роль не в корне)
|
||||
|
||||
Returns:
|
||||
Информация о результате импорта
|
||||
"""
|
||||
# Определение имени роли
|
||||
if not role_name:
|
||||
# Извлекаем имя из URL
|
||||
repo_name = repo_url.rstrip('/').split('/')[-1].replace('.git', '')
|
||||
# Убираем префиксы типа ansible-role-
|
||||
if repo_name.startswith('ansible-role-'):
|
||||
role_name = repo_name.replace('ansible-role-', '')
|
||||
elif repo_name.startswith('ansible-'):
|
||||
role_name = repo_name.replace('ansible-', '')
|
||||
else:
|
||||
role_name = repo_name
|
||||
|
||||
role_dir = self.roles_dir / role_name
|
||||
|
||||
if role_dir.exists():
|
||||
raise ValueError(f"Роль '{role_name}' уже существует. Используйте другое имя или удалите существующую роль.")
|
||||
|
||||
# Клонирование репозитория во временную директорию
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
repo_dir = temp_path / "repo"
|
||||
|
||||
try:
|
||||
repo = Repo.clone_from(repo_url, repo_dir, branch=branch)
|
||||
except GitCommandError as e:
|
||||
raise ValueError(f"Ошибка клонирования репозитория: {str(e)}")
|
||||
|
||||
# Определение исходной директории роли
|
||||
source_dir = repo_dir
|
||||
if subdirectory:
|
||||
source_dir = repo_dir / subdirectory
|
||||
if not source_dir.exists():
|
||||
raise ValueError(f"Поддиректория '{subdirectory}' не найдена в репозитории")
|
||||
|
||||
# Проверка структуры роли
|
||||
if not (source_dir / "tasks").exists() and not (source_dir / "tasks" / "main.yml").exists():
|
||||
# Проверяем, может быть это роль Ansible Galaxy
|
||||
if (source_dir / "meta" / "main.yml").exists():
|
||||
# Это роль, но структура может отличаться
|
||||
pass
|
||||
else:
|
||||
raise ValueError("Не найдена структура роли Ansible (tasks/main.yml или meta/main.yml)")
|
||||
|
||||
# Копирование роли
|
||||
role_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Копируем все стандартные директории и файлы
|
||||
for item in source_dir.iterdir():
|
||||
if item.name.startswith('.'):
|
||||
continue
|
||||
|
||||
dest = role_dir / item.name
|
||||
if item.is_dir():
|
||||
shutil.copytree(item, dest)
|
||||
else:
|
||||
shutil.copy2(item, dest)
|
||||
|
||||
# Обновление deploy.yml
|
||||
from app.core.make_executor import MakeExecutor
|
||||
executor = MakeExecutor()
|
||||
await executor.execute("update-playbooks")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"role_name": role_name,
|
||||
"repo_url": repo_url,
|
||||
"branch": branch,
|
||||
"message": f"Роль '{role_name}' успешно импортирована из {repo_url}"
|
||||
}
|
||||
|
||||
async def import_from_galaxy(
|
||||
self,
|
||||
role_name: str,
|
||||
version: Optional[str] = None,
|
||||
namespace: Optional[str] = None
|
||||
) -> Dict:
|
||||
"""
|
||||
Импорт роли из Ansible Galaxy
|
||||
|
||||
Args:
|
||||
role_name: Имя роли (может быть с namespace: namespace.role_name)
|
||||
version: Версия роли (опционально)
|
||||
namespace: Namespace роли (опционально)
|
||||
|
||||
Returns:
|
||||
Информация о результате импорта
|
||||
"""
|
||||
# Парсинг имени роли
|
||||
if '.' in role_name:
|
||||
parts = role_name.split('.', 1)
|
||||
namespace = parts[0]
|
||||
role_name = parts[1]
|
||||
|
||||
full_role_name = f"{namespace}.{role_name}" if namespace else role_name
|
||||
|
||||
role_dir = self.roles_dir / role_name
|
||||
|
||||
if role_dir.exists():
|
||||
raise ValueError(f"Роль '{role_name}' уже существует")
|
||||
|
||||
# Создание временной директории
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
|
||||
# Установка роли через ansible-galaxy
|
||||
cmd = ["ansible-galaxy", "install", full_role_name]
|
||||
if version:
|
||||
cmd.extend(["--version", version])
|
||||
|
||||
cmd.extend(["--roles-path", str(temp_path)])
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300
|
||||
)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise ValueError(f"Ошибка установки роли из Galaxy: {result.stderr}")
|
||||
|
||||
# Поиск установленной роли
|
||||
installed_role_dir = None
|
||||
for item in temp_path.iterdir():
|
||||
if item.is_dir() and (item / "tasks" / "main.yml").exists():
|
||||
installed_role_dir = item
|
||||
break
|
||||
|
||||
if not installed_role_dir:
|
||||
raise ValueError("Роль не найдена после установки из Galaxy")
|
||||
|
||||
# Копирование роли
|
||||
role_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
for item in installed_role_dir.iterdir():
|
||||
dest = role_dir / item.name
|
||||
if item.is_dir():
|
||||
shutil.copytree(item, dest)
|
||||
else:
|
||||
shutil.copy2(item, dest)
|
||||
|
||||
# Обновление deploy.yml
|
||||
from app.core.make_executor import MakeExecutor
|
||||
executor = MakeExecutor()
|
||||
await executor.execute("update-playbooks")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"role_name": role_name,
|
||||
"galaxy_role": full_role_name,
|
||||
"version": version,
|
||||
"message": f"Роль '{role_name}' успешно импортирована из Ansible Galaxy"
|
||||
}
|
||||
except subprocess.TimeoutExpired:
|
||||
raise ValueError("Таймаут при установке роли из Galaxy")
|
||||
except Exception as e:
|
||||
raise ValueError(f"Ошибка при импорте из Galaxy: {str(e)}")
|
||||
|
||||
async def validate_repo(self, repo_url: str, branch: str = "main") -> Dict:
|
||||
"""Проверка доступности репозитория"""
|
||||
try:
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_path = Path(temp_dir)
|
||||
repo_dir = temp_path / "repo"
|
||||
repo = Repo.clone_from(repo_url, repo_dir, branch=branch, depth=1)
|
||||
|
||||
# Проверка структуры
|
||||
has_tasks = (repo_dir / "tasks" / "main.yml").exists()
|
||||
has_meta = (repo_dir / "meta" / "main.yml").exists()
|
||||
|
||||
return {
|
||||
"valid": True,
|
||||
"has_tasks": has_tasks,
|
||||
"has_meta": has_meta,
|
||||
"is_role": has_tasks or has_meta
|
||||
}
|
||||
except GitCommandError as e:
|
||||
return {
|
||||
"valid": False,
|
||||
"error": str(e)
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"valid": False,
|
||||
"error": str(e)
|
||||
}
|
||||
Reference in New Issue
Block a user