feat: добавлена пометка типа операции (Build/Push) в истории сборок Dockerfile

- Добавлена колонка 'Тип' во все таблицы истории сборок
- Для push операций отображается registry вместо платформ
- Сохранение пользователя при создании push лога
- Исправлена ошибка с logger в push_docker_image endpoint
- Улучшено отображение истории сборок с визуальными индикаторами
This commit is contained in:
Сергей Антропов
2026-02-15 22:59:02 +03:00
parent 23e1a6037b
commit 1fbf9185a2
232 changed files with 38075 additions and 5 deletions

View File

@@ -0,0 +1,985 @@
# Деплой, импорт и экспорт ролей в веб-интерфейсе
**Автор:** Сергей Антропов
**Сайт:** 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