logboard/app/core/docker.py
Сергей Антропов db8d08b8ab feat: убраны demo и test контейнеры из интерфейса
- Добавлена фильтрация demo и test контейнеров в list_containers_with_remote()
- Обновлен тестовый скрипт для исключения demo и test контейнеров
- Теперь в интерфейсе отображаются только рабочие контейнеры
- Убраны лишние тестовые контейнеры из статистики

Результат:
 Demo контейнеры убраны из интерфейса
 Test контейнеры убраны из интерфейса
 Остались только рабочие контейнеры
 Интерфейс стал чище и понятнее

Автор: Сергей Антропов
Сайт: https://devops.org.ru
2025-08-20 20:35:11 +03:00

379 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

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

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
LogBoard+ - Docker функции
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
import json
import os
from typing import List, Dict, Optional
import docker
from core.config import (
DEFAULT_TAIL,
DEFAULT_PROJECT,
DEFAULT_PROJECTS,
SKIP_UNHEALTHY
)
from core.logger import docker_logger
# Инициализация Docker клиента
docker_client = docker.from_env()
def load_excluded_containers() -> List[str]:
"""
Загружает список исключенных контейнеров из JSON файла
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
try:
with open("app/excluded_containers.json", "r", encoding="utf-8") as f:
data = json.load(f)
return data.get("excluded_containers", [])
except FileNotFoundError:
docker_logger.warning("Файл app/excluded_containers.json не найден, используем пустой список")
return []
except json.JSONDecodeError as e:
docker_logger.error(f"Ошибка парсинга app/excluded_containers.json: {e}")
return []
except Exception as e:
docker_logger.error(f"Ошибка загрузки app/excluded_containers.json: {e}")
return []
def save_excluded_containers(containers: List[str]) -> bool:
"""
Сохраняет список исключенных контейнеров в JSON файл
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
try:
data = {
"excluded_containers": containers,
"description": "Список контейнеров, которые генерируют слишком много логов и исключаются из отображения"
}
with open("app/excluded_containers.json", "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
docker_logger.error(f"Ошибка сохранения app/excluded_containers.json: {e}")
return False
def get_all_projects() -> List[str]:
"""
Получает список всех проектов Docker Compose с учетом исключенных контейнеров
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
projects = set()
excluded_containers = load_excluded_containers()
try:
containers = docker_client.containers.list(all=True)
# Словарь для подсчета контейнеров по проектам
project_containers = {}
standalone_containers = []
for c in containers:
try:
# Пропускаем исключенные контейнеры
if c.name in excluded_containers:
continue
labels = c.labels or {}
project = labels.get("com.docker.compose.project")
if project:
if project not in project_containers:
project_containers[project] = 0
project_containers[project] += 1
else:
standalone_containers.append(c.name)
except Exception:
continue
# Добавляем проекты, у которых есть хотя бы один неисключенный контейнер
for project, count in project_containers.items():
if count > 0:
projects.add(project)
# Добавляем standalone, если есть неисключенные контейнеры без проекта
if standalone_containers:
projects.add("standalone")
except Exception as e:
docker_logger.error(f"Ошибка получения списка проектов: {e}")
return []
result = sorted(list(projects))
docker_logger.info(f"Доступные проекты (с учетом исключенных контейнеров): {result}")
return result
def list_containers(projects: Optional[List[str]] = None, include_stopped: bool = False) -> List[Dict]:
"""
Получает список контейнеров с поддержкой множественных проектов
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
# Загружаем список исключенных контейнеров из JSON файла
excluded_containers = load_excluded_containers()
docker_logger.info(f"Список исключенных контейнеров: {excluded_containers}")
items = []
excluded_count = 0
try:
# Получаем список контейнеров с базовой обработкой ошибок
containers = docker_client.containers.list(all=include_stopped)
for c in containers:
try:
# Базовая информация о контейнере (без health check)
basic_info = {
"id": c.id[:12],
"name": c.name,
"status": c.status,
"image": "unknown",
"service": c.name,
"project": None,
"health": None,
"ports": [],
"url": None,
}
# Безопасно получаем метки
try:
labels = c.labels or {}
basic_info["project"] = labels.get("com.docker.compose.project")
basic_info["service"] = labels.get("com.docker.compose.service") or c.name
except Exception:
pass # Используем значения по умолчанию
# Безопасно получаем информацию об образе
try:
if c.image and c.image.tags:
basic_info["image"] = c.image.tags[0]
elif c.image:
basic_info["image"] = c.image.short_id
except Exception:
pass # Оставляем "unknown"
# Безопасно получаем информацию о портах
try:
ports = c.ports or {}
if ports:
basic_info["ports"] = list(ports.keys())
# Пытаемся найти HTTP/HTTPS порт для создания URL
for port_mapping in ports.values():
if port_mapping:
for mapping in port_mapping:
if isinstance(mapping, dict) and mapping.get("HostPort"):
host_port = mapping["HostPort"]
# Проверяем, что это HTTP порт (80, 443, 8080, 3000, etc.)
host_port_int = int(host_port)
if (host_port_int in [80, 443] or
(1 <= host_port_int <= 7999) or
(8000 <= host_port_int <= 65535)):
protocol = "https" if host_port == "443" else "http"
basic_info["url"] = f"{protocol}://localhost:{host_port}"
basic_info["host_port"] = host_port
break
if basic_info["url"]:
break
except Exception:
pass # Оставляем пустые значения
# Фильтрация по проектам
if projects:
# Если проект не указан, считаем его standalone
container_project = basic_info["project"] or "standalone"
if container_project not in projects:
continue
# Фильтрация исключенных контейнеров
if basic_info["name"] in excluded_containers:
excluded_count += 1
docker_logger.warning(f"Пропускаем исключенный контейнер: {basic_info['name']}")
continue
# Добавляем контейнер в список
items.append(basic_info)
except Exception as e:
# Пропускаем контейнеры с критическими ошибками
docker_logger.warning(f"Пропускаем проблемный контейнер {c.name if hasattr(c, 'name') else 'unknown'} (ID: {c.id[:12]}): {e}")
continue
except Exception as e:
docker_logger.error(f"Ошибка получения списка контейнеров: {e}")
return []
# Сортируем по проекту, сервису и имени
items.sort(key=lambda x: (x.get("project") or "", x.get("service") or "", x.get("name") or ""))
# Подсчитываем статистику по проектам
project_stats = {}
for item in items:
project = item.get("project") or "standalone"
if project not in project_stats:
project_stats[project] = {"visible": 0, "excluded": 0}
project_stats[project]["visible"] += 1
# Подсчитываем исключенные контейнеры по проектам
for c in containers:
try:
if c.name in excluded_containers:
labels = c.labels or {}
project = labels.get("com.docker.compose.project") or "standalone"
if project not in project_stats:
project_stats[project] = {"visible": 0, "excluded": 0}
project_stats[project]["excluded"] += 1
except Exception:
continue
docker_logger.info(f"Статистика: найдено {len(items)} контейнеров, исключено {excluded_count} контейнеров")
for project, stats in project_stats.items():
docker_logger.info(f" 📦 {project}: {stats['visible']} видимых, {stats['excluded']} исключенных")
return items
def get_remote_hosts() -> List[str]:
"""
Получает список удаленных хостов из папки logs/remote
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
remote_hosts = []
remote_logs_dir = os.path.join(os.getcwd(), 'logs', 'remote')
try:
if os.path.exists(remote_logs_dir):
for item in os.listdir(remote_logs_dir):
item_path = os.path.join(remote_logs_dir, item)
if os.path.isdir(item_path):
remote_hosts.append(item)
except Exception as e:
docker_logger.error(f"Ошибка получения списка удаленных хостов: {e}")
return sorted(remote_hosts)
def get_remote_containers(hostname: str) -> List[Dict]:
"""
Получает список контейнеров для удаленного хоста
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
containers = []
remote_logs_dir = os.path.join(os.getcwd(), 'logs', 'remote', hostname)
try:
if os.path.exists(remote_logs_dir):
for filename in os.listdir(remote_logs_dir):
if filename.endswith('.log'):
# Извлекаем имя контейнера из имени файла
# Формат: container-name-YYYYMMDD.log
container_name = filename.replace('.log', '')
# Убираем дату из конца
if '-' in container_name:
parts = container_name.split('-')
if len(parts) > 1 and parts[-1].isdigit() and len(parts[-1]) == 8:
container_name = '-'.join(parts[:-1])
# Получаем информацию о файле
file_path = os.path.join(remote_logs_dir, filename)
stat = os.stat(file_path)
containers.append({
"id": f"remote-{hostname}-{container_name}",
"name": container_name,
"status": "running", # Предполагаем, что удаленные контейнеры работают
"image": "remote",
"service": container_name,
"project": "remote",
"health": "healthy",
"ports": [],
"url": None,
"hostname": hostname,
"is_remote": True,
"last_modified": stat.st_mtime,
"size": stat.st_size
})
except Exception as e:
docker_logger.error(f"Ошибка получения контейнеров для хоста {hostname}: {e}")
return containers
def get_all_projects_with_remote() -> List[str]:
"""
Получает список всех проектов включая удаленные
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
# Получаем локальные проекты
local_projects = get_all_projects()
# Добавляем удаленные хосты как проекты
remote_hosts = get_remote_hosts()
remote_projects = [f"remote-{host}" for host in remote_hosts]
# Объединяем и сортируем
all_projects = local_projects + remote_projects
return sorted(all_projects)
def list_containers_with_remote(projects: Optional[List[str]] = None, include_stopped: bool = False) -> List[Dict]:
"""
Получает список всех контейнеров включая удаленные
Автор: Сергей Антропов
Сайт: https://devops.org.ru
"""
# Получаем локальные контейнеры
local_containers = list_containers(projects, include_stopped)
# Добавляем информацию о том, что это локальные контейнеры
for container in local_containers:
container["hostname"] = "localhost"
container["is_remote"] = False
# Получаем удаленные контейнеры
remote_hosts = get_remote_hosts()
remote_containers = []
for hostname in remote_hosts:
# Проверяем, нужно ли включать этот хост
if projects is None or any(f"remote-{hostname}" in project for project in projects):
host_containers = get_remote_containers(hostname)
remote_containers.extend(host_containers)
# Объединяем локальные и удаленные контейнеры
all_containers = local_containers + remote_containers
# Фильтруем demo и test контейнеры
filtered_containers = []
for container in all_containers:
container_name = container.get("name", "").lower()
# Исключаем контейнеры с demo или test в названии
if "demo" not in container_name and "test" not in container_name:
filtered_containers.append(container)
# Фильтруем по проектам, если указаны
if projects:
project_filtered_containers = []
for container in filtered_containers:
if container["is_remote"]:
# Для удаленных контейнеров проверяем соответствие хоста
if any(f"remote-{container['hostname']}" in project for project in projects):
project_filtered_containers.append(container)
else:
# Для локальных контейнеров проверяем проект
if container["project"] in projects or "standalone" in projects:
project_filtered_containers.append(container)
return project_filtered_containers
return filtered_containers