#!/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, include_stopped: bool = False) -> List[Dict]: """ Получает список контейнеров для удаленного хоста Автор: Сергей Антропов Сайт: https://devops.org.ru Args: hostname: Имя удаленного хоста include_stopped: Включать ли остановленные контейнеры """ 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) # Проверяем, активен ли контейнер (логи обновлялись в последние 5 минут) import time current_time = time.time() last_modified = stat.st_mtime is_active = (current_time - last_modified) < 300 # 5 минут = 300 секунд # Добавляем контейнер только если он активен или если include_stopped=True if is_active or include_stopped: containers.append({ "id": f"remote-{hostname}-{container_name}", "name": container_name, "status": "running" if is_active else "stopped", "image": "remote", "service": container_name, "project": "remote", "health": "healthy" if is_active else "unhealthy", "ports": [], "url": None, "hostname": hostname, "is_remote": True, "last_modified": last_modified, "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, include_stopped) 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