Files
DevOpsLab/app/services/import_service.py
Сергей Антропов 1fbf9185a2 feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile
- Добавлена колонка 'Тип' во все таблицы истории сборок
- Для push операций отображается registry вместо платформ
- Сохранение пользователя при создании push лога
- Исправлена ошибка с logger в push_docker_image endpoint
- Улучшено отображение истории сборок с визуальными индикаторами
2026-02-15 22:59:02 +03:00

234 lines
9.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
Сервис для импорта ролей из репозиториев и 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)
}