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

986 lines
57 KiB
Markdown
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.

# Деплой, импорт и экспорт ролей в веб-интерфейсе
**Автор:** Сергей Антропов
**Сайт:** 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 деплоя
```python
# 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. Сохранение истории деплоев
**Автоматическое сохранение в БД:**
```python
# 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. Реализация экспорта
```python
# 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. Реализация импорта
```python
# 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. База данных для истории
```python
# 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 для логирования команд
```python
# 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
```python
# 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