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

57 KiB
Raw Blame History

Деплой, импорт и экспорт ролей в веб-интерфейсе

Автор: Сергей Антропов
Сайт: https://devops.org.ru

🎯 Обзор

Этот документ описывает функциональность деплоя на живые серверы, импорта и экспорта ролей через веб-интерфейс.


1. 🚀 Деплой на живые серверы

1.1. Страница деплоя роли

URL: /roles/{role_name}/deploy

Интерфейс:

┌─────────────────────────────────────────────────────────┐
│  Деплой роли: nginx на продакшн серверы                 │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  ⚠️  ВНИМАНИЕ: Вы собираетесь изменить реальные серверы!│
│                                                         │
│  📋 Шаг 1: Выбор inventory                             │
│  ┌─────────────────────────────────────────────────┐   │
│  │ ○ Использовать существующий inventory          │   │
│  │   [production ▼] [staging] [development]      │   │
│  │                                                 │   │
│  │ ● Создать/редактировать inventory              │   │
│  │   [Открыть редактор inventory]                 │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📝 Шаг 2: Настройка переменных                        │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Переменные для продакшн:                        │   │
│  │                                                 │   │
│  │ nginx_version: [1.25.0]                         │   │
│  │ nginx_worker_processes: [4]                      │   │
│  │ nginx_worker_connections: [2048]                 │   │
│  │ nginx_sites:                                     │   │
│  │   - name: example.com                           │   │
│  │     root: /var/www/html                         │   │
│  │     ssl: ☑                                       │   │
│  │                                                 │   │
│  │ [📥 Загрузить из шаблона]                       │   │
│  │ [💾 Сохранить как шаблон]                       │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  ⚙️  Шаг 3: Параметры деплоя                           │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Режим:                                          │   │
│  │ ○ Dry-run (только проверка, без изменений)     │   │
│  │ ● Реальный деплой                               │   │
│  │                                                 │   │
│  │ Опции:                                          │   │
│  │ ☑ Проверка подключения перед деплоем           │   │
│  │ ☑ Проверка синтаксиса                          │   │
│  │ ☐ Только определенные теги                     │   │
│  │   [web,config]                                  │   │
│  │ ☐ Только определенные хосты                    │   │
│  │   [web1,web2]                                   │   │
│  │ ☐ Лимит хостов (для постепенного деплоя)       │   │
│  │   [2 из 5]                                      │   │
│  │                                                 │   │
│  │ Стратегия деплоя:                              │   │
│  │ ○ Все хосты одновременно                       │   │
│  │ ● Постепенно (по одному)                       │   │
│  │ ○ По группам (сначала web, потом db)           │   │
│  │                                                 │   │
│  │ Rollback:                                       │   │
│  │ ☑ Создать backup перед деплоем                  │   │
│  │ ☑ Включить автоматический rollback при ошибке  │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  🔐 Шаг 4: Безопасность                                 │
│  ┌─────────────────────────────────────────────────┐   │
│  │ ☑ Использовать Vault для секретов               │   │
│  │ ☑ Шифровать SSH ключи                           │   │
│  │ ☑ Требовать подтверждение для критических операций│
│  │                                                 │   │
│  │ Подтверждение:                                  │   │
│  │ [ ] Я понимаю, что это изменит продакшн серверы │   │
│  │ [ ] Я проверил переменные                        │   │
│  │ [ ] У меня есть backup                          │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  [🔍 Dry-run]  [🚀 Запустить деплой]                  │
└─────────────────────────────────────────────────────────┘

1.2. Live мониторинг деплоя

После запуска деплоя открывается страница с live логами:

┌─────────────────────────────────────────────────────────┐
│  Деплой роли: nginx | Статус: 🟢 Running                │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  📊 Прогресс:                                          │
│  ┌─────────────────────────────────────────────────┐   │
│  │ ████████████████░░░░ 80%                        │   │
│  │ Хостов обработано: 4 из 5                       │   │
│  │ Текущий хост: web3.example.com                   │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  🖥️  Статус хостов:                                    │
│  ┌─────────────────────────────────────────────────┐   │
│  │ ✅ web1.example.com    [Успешно] (2m 15s)        │   │
│  │ ✅ web2.example.com    [Успешно] (2m 10s)        │   │
│  │ 🟢 web3.example.com    [В процессе...]           │   │
│  │ ⏳ web4.example.com    [Ожидание...]            │   │
│  │ ⏳ web5.example.com    [Ожидание...]            │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📝 Live логи (WebSocket):                             │
│  ┌─────────────────────────────────────────────────┐   │
│  │ [10:30:15] PLAY [web_servers] ****************** │   │
│  │ [10:30:16] TASK [nginx : Install packages]      │   │
│  │ [10:30:17] changed: [web1.example.com]          │   │
│  │ [10:30:18] TASK [nginx : Configure nginx]      │   │
│  │ [10:30:19] changed: [web1.example.com]          │   │
│  │ [10:30:20] TASK [nginx : Start nginx]           │   │
│  │ [10:30:21] changed: [web1.example.com]          │   │
│  │ [10:30:22] PLAY RECAP ************************** │   │
│  │ [10:30:23] web1.example.com : ok=5 changed=3   │   │
│  │ ...                                             │   │
│  │                                                  │   │
│  │ [Автоскролл: ☑] [Очистить] [📥 Скачать]          │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📈 Статистика:                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Успешно: 2                                       │   │
│  │ В процессе: 1                                   │   │
│  │ Ожидание: 2                                      │   │
│  │ Ошибок: 0                                        │   │
│  │ Среднее время: 2m 12s                           │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  [⏸️ Пауза]  [⏹️ Остановить]  [🔄 Retry failed]      │
└─────────────────────────────────────────────────────────┘

1.3. WebSocket реализация для live деплоя

# app/api/v1/endpoints/deploy.py
from fastapi import WebSocket, APIRouter
from app.core.make_executor import MakeExecutor
from app.services.deploy_service import DeployService

router = APIRouter()

@router.websocket("/ws/deploy/{deploy_id}")
async def deploy_websocket(websocket: WebSocket, deploy_id: str):
    await websocket.accept()
    
    # Получение параметров деплоя из БД
    deploy_config = await get_deploy_config(deploy_id)
    
    # Запуск ansible-playbook с потоковым выводом
    executor = MakeExecutor()
    process = await executor.execute_stream(
        f"role deploy",
        args=[
            "--inventory", deploy_config["inventory"],
            "--extra-vars", json.dumps(deploy_config["vars"]),
            "--limit", deploy_config.get("limit", ""),
            "--tags", deploy_config.get("tags", "")
        ]
    )
    
    # Отправка логов в реальном времени
    current_host = None
    async for line in process.stdout:
        decoded_line = line.decode()
        
        # Парсинг строки для определения хоста
        host_match = re.search(r'\[([^\]]+)\]', decoded_line)
        if host_match:
            current_host = host_match.group(1)
        
        # Отправка с метаданными
        await websocket.send_json({
            "type": "log",
            "host": current_host,
            "data": decoded_line,
            "timestamp": datetime.now().isoformat(),
            "level": detect_log_level(decoded_line)  # info, warning, error
        })
        
        # Отправка статуса хоста
        if "PLAY RECAP" in decoded_line or "ok=" in decoded_line:
            status = parse_play_recap(decoded_line)
            await websocket.send_json({
                "type": "host_status",
                "host": current_host,
                "status": status["status"],  # success, failed, changed
                "changed": status.get("changed", 0),
                "ok": status.get("ok", 0),
                "failed": status.get("failed", 0)
            })
    
    # Финальный статус
    await websocket.send_json({
        "type": "complete",
        "status": "success" if process.returncode == 0 else "failed",
        "summary": await generate_deploy_summary(deploy_id)
    })

1.4. Сохранение истории деплоев

Автоматическое сохранение в БД:

# app/db/models.py
class DeployHistory(Base):
    __tablename__ = "deploy_history"
    
    id = Column(Integer, primary_key=True)
    role_name = Column(String, nullable=False)
    inventory = Column(String, nullable=False)
    started_at = Column(DateTime, nullable=False)
    completed_at = Column(DateTime)
    status = Column(String)  # success, failed, cancelled
    duration = Column(Integer)  # секунды
    
    # Параметры деплоя
    variables = Column(JSON)  # Переменные роли
    limit = Column(String)  # Ограничение хостов
    tags = Column(String)  # Теги для выполнения
    
    # Результаты
    total_hosts = Column(Integer)
    successful_hosts = Column(Integer)
    failed_hosts = Column(Integer)
    changed_hosts = Column(Integer)
    
    # Логи
    logs = Column(Text)  # Полные логи
    summary = Column(Text)  # Краткое резюме
    
    # Пользователь
    user = Column(String)
    dry_run = Column(Boolean, default=False)

Интерфейс истории:

┌─────────────────────────────────────────────────────────┐
│  История деплоев                                        │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  📋 Фильтры:                                            │
│  [Роль: nginx ▼] [Статус: Все ▼] [Дата: Последние 7 дней]│
│                                                         │
│  📊 Таблица деплоев:                                    │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Дата      | Роль  | Inventory | Статус | Действия│
│  ├─────────────────────────────────────────────────┤   │
│  │ 15.01 10:30| nginx | production| ✅     | [👁️][📥]│
│  │ 15.01 09:15| nginx | staging   | ✅     | [👁️][📥]│
│  │ 14.01 16:45| docker| production| ❌     | [👁️][📥]│
│  │ 14.01 14:20| python| production| ✅     | [👁️][📥]│
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  [📥 Экспорт в CSV]  [📥 Экспорт в JSON]              │
└─────────────────────────────────────────────────────────┘

2. 📤 Экспорт ролей в отдельные Git репозитории

2.1. Страница экспорта роли

URL: /roles/{role_name}/export

Интерфейс:

┌─────────────────────────────────────────────────────────┐
│  Экспорт роли: nginx в отдельный репозиторий            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  📋 Шаг 1: Настройка репозитория                        │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Тип экспорта:                                    │   │
│  │ ○ Создать новый репозиторий                     │   │
│  │ ● Использовать существующий                     │   │
│  │                                                 │   │
│  │ URL репозитория:                                │   │
│  │ [https://github.com/user/ansible-role-nginx]    │   │
│  │                                                 │   │
│  │ Ветка: [main ▼] [master] [develop]             │   │
│  │                                                 │   │
│  │ Аутентификация:                                 │   │
│  │ ○ SSH ключ (из настроек)                        │   │
│  │ ● Personal Access Token                         │   │
│  │   [token: ****************]                     │   │
│  │                                                 │   │
│  │ [🔐 Тест подключения]                           │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📦 Шаг 2: Выбор компонентов для экспорта              │
│  ┌─────────────────────────────────────────────────┐   │
│  │ ☑ Структура роли (tasks, handlers, etc.)       │   │
│  │ ☑ defaults/main.yml                             │   │
│  │ ☑ vars/main.yml                                 │   │
│  │ ☑ templates/                                    │   │
│  │ ☑ files/                                        │   │
│  │ ☑ meta/main.yml                                 │   │
│  │ ☑ README.md                                      │   │
│  │ ☐ molecule/ (конфигурация тестирования)         │   │
│  │ ☐ tests/ (тестовые playbook'и)                  │   │
│  │ ☐ .github/ (CI/CD workflows)                    │   │
│  │                                                 │   │
│  │ ⚠️  Секреты (vars/main.yml):                    │   │
│  │ ○ Экспортировать зашифрованными                 │   │
│  │ ● Экспортировать без секретов (оставить пустыми)│
│  │ ○ Не экспортировать                            │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📝 Шаг 3: Метаданные и версионирование                │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Версия: [1.0.0]                                 │   │
│  │ Тег: [v1.0.0] (создать git tag)                 │   │
│  │                                                 │   │
│  │ Описание изменений:                             │   │
│  │ [Исправлена конфигурация nginx...]              │   │
│  │                                                 │   │
│  │ CHANGELOG.md:                                    │   │
│  │ ☑ Автоматически обновить                        │   │
│  │ ☑ Создать release notes                         │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📋 Шаг 4: Предпросмотр                                │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Структура репозитория:                           │   │
│  │ ansible-role-nginx/                             │   │
│  │ ├── tasks/                                      │   │
│  │ ├── handlers/                                   │   │
│  │ ├── defaults/                                   │   │
│  │ ├── vars/                                       │   │
│  │ ├── templates/                                  │   │
│  │ ├── files/                                      │   │
│  │ ├── meta/                                       │   │
│  │ ├── README.md                                   │   │
│  │ └── .gitignore                                  │   │
│  │                                                 │   │
│  │ Файлов для экспорта: 47                         │   │
│  │ Размер: 2.3 MB                                  │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  [👁️ Предпросмотр файлов]  [📤 Экспортировать]        │
└─────────────────────────────────────────────────────────┘

2.2. Процесс экспорта с прогрессом

После нажатия "Экспортировать":

┌─────────────────────────────────────────────────────────┐
│  Экспорт роли: nginx                                    │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  📊 Прогресс:                                          │
│  ┌─────────────────────────────────────────────────┐   │
│  │ ████████████████████░░ 90%                       │   │
│  │ Этап: Загрузка в репозиторий                    │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📋 Этапы:                                              │
│  ✅ Подготовка файлов                                  │   │
│  ✅ Обработка секретов                                 │   │
│  ✅ Создание .gitignore                                │   │
│  ✅ Клонирование репозитория                           │   │
│  ✅ Копирование файлов                                 │   │
│  🟢 Загрузка в репозиторий (git push)                 │   │
│  ⏳ Создание тега                                      │   │
│  ⏳ Создание release                                    │   │
│                                                         │
│  📝 Логи:                                              │
│  ┌─────────────────────────────────────────────────┐   │
│  │ [10:35:15] Подготовка файлов...                 │   │
│  │ [10:35:16] Обработка секретов...                │   │
│  │ [10:35:17] Клонирование репозитория...          │   │
│  │ [10:35:20] Копирование 47 файлов...             │   │
│  │ [10:35:22] git add .                             │   │
│  │ [10:35:23] git commit -m "v1.0.0: ..."          │   │
│  │ [10:35:25] git push origin main...              │   │
│  │ ...                                             │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  [✅ Экспорт завершен]  [🔗 Открыть репозиторий]      │
└─────────────────────────────────────────────────────────┘

2.3. Реализация экспорта

# app/services/export_service.py
from git import Repo
import shutil
from pathlib import Path

class ExportService:
    async def export_role(
        self,
        role_name: str,
        repo_url: str,
        branch: str = "main",
        version: str = None,
        components: List[str] = None,
        include_secrets: bool = False
    ) -> dict:
        """Экспорт роли в Git репозиторий"""
        
        # 1. Подготовка файлов
        role_path = Path(f"roles/{role_name}")
        temp_dir = Path(f"/tmp/export_{role_name}")
        temp_dir.mkdir(exist_ok=True)
        
        # 2. Копирование выбранных компонентов
        components = components or ["tasks", "handlers", "defaults", "meta", "README.md"]
        for component in components:
            src = role_path / component
            if src.exists():
                if src.is_dir():
                    shutil.copytree(src, temp_dir / component)
                else:
                    shutil.copy2(src, temp_dir / component)
        
        # 3. Обработка секретов
        if "vars" in components:
            vars_file = role_path / "vars" / "main.yml"
            if vars_file.exists():
                if include_secrets:
                    # Копировать зашифрованным
                    shutil.copy2(vars_file, temp_dir / "vars" / "main.yml")
                else:
                    # Создать без секретов
                    self.create_vars_without_secrets(vars_file, temp_dir / "vars" / "main.yml")
        
        # 4. Создание .gitignore
        self.create_gitignore(temp_dir)
        
        # 5. Клонирование/обновление репозитория
        repo_dir = Path(f"/tmp/repo_{role_name}")
        if repo_dir.exists():
            repo = Repo(repo_dir)
            repo.remote().pull()
        else:
            repo = Repo.clone_from(repo_url, repo_dir, branch=branch)
        
        # 6. Копирование файлов в репозиторий
        for item in temp_dir.iterdir():
            dest = repo_dir / item.name
            if dest.exists():
                if dest.is_dir():
                    shutil.rmtree(dest)
                else:
                    dest.unlink()
            if item.is_dir():
                shutil.copytree(item, dest)
            else:
                shutil.copy2(item, dest)
        
        # 7. Коммит и push
        repo.git.add(A=True)
        commit_message = f"{version}: {self.get_changelog_entry(role_name, version)}"
        repo.index.commit(commit_message)
        repo.remote().push()
        
        # 8. Создание тега
        if version:
            repo.create_tag(f"v{version}")
            repo.remote().push(tags=True)
        
        # 9. Очистка
        shutil.rmtree(temp_dir)
        shutil.rmtree(repo_dir)
        
        return {
            "success": True,
            "repo_url": repo_url,
            "version": version,
            "commit": repo.head.commit.hexsha
        }

3. 📥 Импорт ролей из других репозиториев

3.1. Страница импорта роли

URL: /roles/import

Интерфейс:

┌─────────────────────────────────────────────────────────┐
│  Импорт роли из репозитория                             │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  📋 Шаг 1: Источник роли                                │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Тип источника:                                  │   │
│  │ ○ Git репозиторий                               │   │
│  │ ● Ansible Galaxy                                 │   │
│  │ ○ Локальный архив (tar.gz, zip)                 │   │
│  │ ○ URL (прямая ссылка на архив)                  │   │
│  │                                                 │   │
│  │ ┌─ Git репозиторий ─────────────────────────┐  │   │
│  │ │ URL:                                       │  │   │
│  │ │ [https://github.com/user/ansible-role-nginx]│  │   │
│  │ │                                            │  │   │
│  │ │ Ветка/тег: [main ▼] [v1.0.0] [latest]     │  │   │
│  │ │                                            │  │   │
│  │ │ Путь к роли (если не в корне):             │  │   │
│  │ │ [/roles/nginx] (оставить пустым если в корне)│  │   │
│  │ └────────────────────────────────────────────┘  │   │
│  │                                                 │   │
│  │ ┌─ Ansible Galaxy ───────────────────────────┐  │   │
│  │ │ Роль: [geerlingguy.nginx]                  │  │   │
│  │ │ Версия: [latest ▼] [1.0.0] [0.9.0]        │  │   │
│  │ └────────────────────────────────────────────┘  │   │
│  │                                                 │   │
│  │ [🔍 Проверить доступность]                     │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📝 Шаг 2: Настройка импорта                           │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Имя роли в проекте:                             │   │
│  │ [nginx] (автоматически из источника)            │   │
│  │                                                 │   │
│  │ Действие при конфликте:                         │   │
│  │ ○ Пропустить (не импортировать)                 │   │
│  │ ● Перезаписать существующую роль                │   │
│  │ ○ Создать копию (nginx-imported)                │   │
│  │                                                 │   │
│  │ Импортировать:                                  │   │
│  │ ☑ Структуру роли                                │   │
│  │ ☑ molecule конфигурацию (если есть)             │   │
│  │ ☑ tests/ (тестовые playbook'и)                  │   │
│  │ ☐ .github/ (CI/CD workflows)                    │   │
│  │ ☐ Документацию (если отличается)                │   │
│  │                                                 │   │
│  │ Обработка зависимостей:                         │   │
│  │ ○ Импортировать зависимости автоматически       │   │
│  │ ● Показать список для подтверждения             │   │
│  │ ○ Пропустить зависимости                        │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📋 Шаг 3: Предпросмотр                                │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Информация о роли:                              │   │
│  │ ├─ Название: nginx                              │   │
│  │ ├─ Версия: 1.0.0                                │   │
│  │ ├─ Автор: geerlingguy                           │   │
│  │ ├─ Описание: Install and configure nginx        │   │
│  │ ├─ Платформы: Ubuntu, Debian, CentOS, RHEL     │   │
│  │ └─ Зависимости: common (geerlingguy.common)     │   │
│  │                                                 │   │
│  │ Структура:                                       │   │
│  │ roles/nginx/                                     │   │
│  │ ├── tasks/ (12 файлов)                          │   │
│  │ ├── handlers/ (2 файла)                         │   │
│  │ ├── defaults/ (1 файл)                          │   │
│  │ ├── templates/ (5 файлов)                       │   │
│  │ ├── meta/ (1 файл)                              │   │
│  │ └── README.md                                    │   │
│  │                                                 │   │
│  │ Файлов: 23                                       │   │
│  │ Размер: 1.8 MB                                   │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  [👁️ Предпросмотр файлов]  [📥 Импортировать]        │
└─────────────────────────────────────────────────────────┘

3.2. Процесс импорта

После нажатия "Импортировать":

┌─────────────────────────────────────────────────────────┐
│  Импорт роли: nginx                                     │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  📊 Прогресс:                                          │
│  ┌─────────────────────────────────────────────────┐   │
│  │ ████████████████████░░ 95%                       │   │
│  │ Этап: Интеграция в проект                       │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📋 Этапы:                                              │
│  ✅ Клонирование репозитория                          │   │
│  ✅ Извлечение роли                                   │   │
│  ✅ Проверка структуры                                │   │
│  ✅ Копирование файлов                                │   │
│  🟢 Интеграция в проект (обновление deploy.yml)      │   │
│  ⏳ Импорт зависимостей                                │   │
│                                                         │
│  📝 Логи:                                              │
│  ┌─────────────────────────────────────────────────┐   │
│  │ [10:40:15] Клонирование репозитория...         │   │
│  │ [10:40:18] Извлечение роли nginx...            │   │
│  │ [10:40:19] Проверка структуры...               │   │
│  │ [10:40:20] Копирование 23 файлов...            │   │
│  │ [10:40:22] Обновление roles/deploy.yml...      │   │
│  │ [10:40:23] Импорт зависимости: common...      │   │
│  │ ...                                             │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  [✅ Импорт завершен]  [👁️ Просмотреть роль]         │
└─────────────────────────────────────────────────────────┘

3.3. Реализация импорта

# app/services/import_service.py
from git import Repo
import shutil
from pathlib import Path
import ansible_galaxy

class ImportService:
    async def import_role(
        self,
        source: str,
        source_type: str,  # git, galaxy, archive, url
        role_name: str = None,
        version: str = None,
        conflict_action: str = "overwrite"  # skip, overwrite, copy
    ) -> dict:
        """Импорт роли из внешнего источника"""
        
        role_path = Path(f"roles/{role_name}")
        
        # Проверка конфликтов
        if role_path.exists() and conflict_action == "skip":
            return {"success": False, "error": "Роль уже существует"}
        
        # Импорт в зависимости от типа
        if source_type == "git":
            await self.import_from_git(source, role_name, version)
        elif source_type == "galaxy":
            await self.import_from_galaxy(source, role_name, version)
        elif source_type == "archive":
            await self.import_from_archive(source, role_name)
        elif source_type == "url":
            await self.import_from_url(source, role_name)
        
        # Обработка конфликтов
        if conflict_action == "copy" and role_path.exists():
            role_path = Path(f"roles/{role_name}-imported")
        
        # Интеграция в проект
        await self.integrate_role(role_name)
        
        # Импорт зависимостей
        dependencies = await self.get_role_dependencies(role_name)
        if dependencies:
            await self.import_dependencies(dependencies)
        
        return {
            "success": True,
            "role_name": role_name,
            "path": str(role_path),
            "dependencies": dependencies
        }
    
    async def import_from_git(self, repo_url: str, role_name: str, version: str = None):
        """Импорт из Git репозитория"""
        temp_dir = Path(f"/tmp/import_{role_name}")
        temp_dir.mkdir(exist_ok=True)
        
        # Клонирование
        repo = Repo.clone_from(repo_url, temp_dir)
        if version:
            repo.git.checkout(version)
        
        # Поиск роли в репозитории
        role_source = self.find_role_in_repo(temp_dir, role_name)
        
        # Копирование
        role_path = Path(f"roles/{role_name}")
        if role_path.exists():
            shutil.rmtree(role_path)
        shutil.copytree(role_source, role_path)
        
        # Очистка
        shutil.rmtree(temp_dir)
    
    async def import_from_galaxy(self, role_id: str, role_name: str, version: str = None):
        """Импорт из Ansible Galaxy"""
        # Использование ansible-galaxy
        import subprocess
        cmd = ["ansible-galaxy", "install", role_id]
        if version:
            cmd.extend(["--version", version])
        subprocess.run(cmd, cwd="roles")
        
        # Переименование если нужно
        installed_name = role_id.replace(".", "-")
        if installed_name != role_name:
            Path(f"roles/{installed_name}").rename(f"roles/{role_name}")
    
    async def integrate_role(self, role_name: str):
        """Интеграция роли в проект"""
        # Обновление roles/deploy.yml
        await self.update_deploy_yml(role_name)
        
        # Создание molecule конфигурации если нужно
        await self.setup_molecule_config(role_name)
        
        # Обновление документации
        await self.update_project_docs(role_name)

4. 📊 История команд и результатов

4.1. База данных для истории

# app/db/models.py
class CommandHistory(Base):
    __tablename__ = "command_history"
    
    id = Column(Integer, primary_key=True)
    command = Column(String, nullable=False)  # make role test nginx
    command_type = Column(String)  # test, deploy, export, import
    role_name = Column(String)
    started_at = Column(DateTime, nullable=False)
    completed_at = Column(DateTime)
    status = Column(String)  # success, failed, cancelled, running
    duration = Column(Integer)  # секунды
    
    # Параметры
    parameters = Column(JSON)  # Все параметры команды
    
    # Результаты
    stdout = Column(Text)
    stderr = Column(Text)
    return_code = Column(Integer)
    
    # Пользователь
    user = Column(String)
    ip_address = Column(String)
    
    # Связи
    test_result_id = Column(Integer, ForeignKey("test_results.id"))
    deploy_id = Column(Integer, ForeignKey("deploy_history.id"))

4.2. Интерфейс истории команд

┌─────────────────────────────────────────────────────────┐
│  История команд                                          │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  📋 Фильтры:                                            │
│  [Тип: Все ▼] [Роль: Все ▼] [Статус: Все ▼]           │
│  [Дата: Последние 7 дней ▼]                            │
│                                                         │
│  📊 Таблица команд:                                      │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Дата      | Команда | Роль | Статус | Действия │   │
│  ├─────────────────────────────────────────────────┤   │
│  │ 15.01 10:30| test   | nginx| ✅     | [👁️][📥]│
│  │ 15.01 09:15| deploy | nginx| ✅     | [👁️][📥]│
│  │ 15.01 08:45| export | nginx| ✅     | [👁️][📥]│
│  │ 14.01 16:30| import | docker| ✅     | [👁️][📥]│
│  │ 14.01 15:20| test   | python| ❌     | [👁️][📥]│
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📈 Статистика:                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Всего команд: 127                               │   │
│  │ Успешных: 115 (90.6%)                           │   │
│  │ Ошибок: 12 (9.4%)                               │   │
│  │ Среднее время: 2m 15s                           │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  [📥 Экспорт в CSV]  [📥 Экспорт в JSON]              │
└─────────────────────────────────────────────────────────┘

4.3. Детали команды

При клике на команду:

┌─────────────────────────────────────────────────────────┐
│  Детали команды: make role test nginx default           │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  📋 Информация:                                         │
│  ┌─────────────────────────────────────────────────┐   │
│  │ Команда: make role test nginx default           │   │
│  │ Тип: test                                        │   │
│  │ Роль: nginx                                      │   │
│  │ Preset: default                                  │   │
│  │ Статус: ✅ Успешно                               │   │
│  │ Начало: 15.01.2024 10:30:15                     │   │
│  │ Завершение: 15.01.2024 10:32:30                 │   │
│  │ Длительность: 2m 15s                            │   │
│  │ Пользователь: admin                             │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📝 Параметры:                                          │
│  ┌─────────────────────────────────────────────────┐   │
│  │ {                                                │   │
│  │   "role": "nginx",                              │   │
│  │   "preset": "default",                           │   │
│  │   "parallel": false,                             │   │
│  │   "lint": true                                   │   │
│  │ }                                                │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  📄 Вывод (stdout):                                     │
│  ┌─────────────────────────────────────────────────┐   │
│  │ [Показать полный вывод]                        │   │
│  │ ...                                             │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  ❌ Ошибки (stderr):                                    │
│  ┌─────────────────────────────────────────────────┐   │
│  │ (нет ошибок)                                   │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  [📥 Скачать логи]  [🔄 Повторить команду]            │
└─────────────────────────────────────────────────────────┘

5. 🔄 Автоматическое сохранение истории

5.1. Middleware для логирования команд

# app/core/command_logger.py
from app.db.database import SessionLocal
from app.db.models import CommandHistory
from datetime import datetime

class CommandLogger:
    async def log_command(
        self,
        command: str,
        command_type: str,
        parameters: dict,
        user: str = None
    ) -> int:
        """Сохранение команды в историю"""
        db = SessionLocal()
        history = CommandHistory(
            command=command,
            command_type=command_type,
            role_name=parameters.get("role_name"),
            started_at=datetime.now(),
            status="running",
            parameters=parameters,
            user=user
        )
        db.add(history)
        db.commit()
        return history.id
    
    async def update_command(
        self,
        history_id: int,
        status: str,
        stdout: str = None,
        stderr: str = None,
        return_code: int = None
    ):
        """Обновление результата команды"""
        db = SessionLocal()
        history = db.query(CommandHistory).filter_by(id=history_id).first()
        history.completed_at = datetime.now()
        history.status = status
        history.stdout = stdout
        history.stderr = stderr
        history.return_code = return_code
        history.duration = (history.completed_at - history.started_at).seconds
        db.commit()

5.2. Интеграция с MakeExecutor

# app/core/make_executor.py
class MakeExecutor:
    def __init__(self):
        self.logger = CommandLogger()
    
    async def execute(
        self,
        command: str,
        args: List[str] = None,
        user: str = None
    ) -> Dict:
        """Выполнение команды с логированием"""
        # Сохранение в историю
        history_id = await self.logger.log_command(
            command=command,
            command_type=self.detect_command_type(command),
            parameters=self.parse_parameters(command, args),
            user=user
        )
        
        # Выполнение команды
        cmd = ["make"] + command.split() + (args or [])
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
            cwd=PROJECT_ROOT
        )
        
        # Обновление истории
        await self.logger.update_command(
            history_id=history_id,
            status="success" if result.returncode == 0 else "failed",
            stdout=result.stdout,
            stderr=result.stderr,
            return_code=result.returncode
        )
        
        return {
            "success": result.returncode == 0,
            "stdout": result.stdout,
            "stderr": result.stderr,
            "history_id": history_id
        }

📝 Резюме

Все функции доступны через веб-интерфейс:

  1. Деплой на живые серверы:

    • Выбор inventory
    • Настройка переменных
    • Live логи через WebSocket
    • Сохранение истории
  2. Экспорт ролей:

    • В отдельные Git репозитории
    • С версионированием и тегами
    • С обработкой секретов
  3. Импорт ролей:

    • Из Git репозиториев
    • Из Ansible Galaxy
    • С автоматической интеграцией
  4. История команд:

    • Автоматическое сохранение всех команд
    • Детальные логи
    • Статистика и аналитика

Всё с live обновлениями, сохранением истории и удобным интерфейсом!


Автор: Сергей Антропов
Сайт: https://devops.org.ru