""" Docker Container Monitor Checks status of all Docker containers on the Nave. Uses: docker ps command (requires Docker socket access) Expected containers (from vault/servizi.txt): 14 containers total including paperless_broker, paperless_db, guacamole_postgres, guacamole_guacd, etc. """ import subprocess from typing import Dict, List class DockerMonitor: """Monitor Docker containers health""" # We don't hardcode expected containers - we check what's running def check(self) -> Dict: """ Check all Docker containers. Returns: { 'status': 'healthy' | 'warning' | 'critical', 'containers_total': X, 'containers_running': Y, 'containers_down': [list of down containers], 'details': [{name, status, uptime}, ...] } """ try: # Run: docker ps -a --format "{{.Names}}\t{{.State}}\t{{.Status}}" result = subprocess.run( ['docker', 'ps', '-a', '--format', '{{.Names}}\t{{.State}}\t{{.Status}}'], capture_output=True, text=True, timeout=10 ) if result.returncode != 0: return { 'status': 'critical', 'error': result.stderr, 'message': f'Docker command failed: {result.stderr}' } # Parse output containers = self._parse_docker_output(result.stdout) # Check running vs stopped running = [c for c in containers if c['state'] == 'running'] down = [c['name'] for c in containers if c['state'] != 'running'] # Determine status if len(down) > 0: status = 'critical' if len(down) > 2 else 'warning' else: status = 'healthy' return { 'status': status, 'containers_total': len(containers), 'containers_running': len(running), 'containers_down': down, 'details': containers, 'message': self._create_message(len(running), len(containers), down) } except FileNotFoundError: return { 'status': 'critical', 'error': 'Docker not installed', 'message': 'Docker command not found' } except Exception as e: return { 'status': 'critical', 'error': str(e), 'message': f'Docker check failed: {e}' } def _parse_docker_output(self, output: str) -> List[Dict]: """Parse docker ps output into list of dicts""" containers = [] for line in output.strip().split('\n'): if not line: continue parts = line.split('\t') if len(parts) >= 3: name = parts[0] state = parts[1].lower() status = parts[2] containers.append({ 'name': name, 'state': state, 'status': status }) return containers def _create_message(self, running_count: int, total_count: int, down: List[str]) -> str: """Create human-readable status message""" if not down: return f"All {running_count} containers running" else: down_names = ', '.join(down[:3]) # Show first 3 if len(down) > 3: down_names += f" (+{len(down) - 3} more)" return f"{running_count}/{total_count} running. Down: {down_names}"