# plugins/docker/__init__.py from __future__ import annotations from typing import TYPE_CHECKING if TYPE_CHECKING: from quart import Quart _app: "Quart | None" = None def setup(app: "Quart") -> None: global _app _app = app from .models import DockerContainer # noqa: F401 — register with Base # Publish the per-host persist hook so the host agent can hand off the # `docker` array from each sample without importing our models (opportunistic # synergy — host_agent gates on has_capability and no-ops if we're disabled). # required_role=viewer: this is a trusted server-side data-plane write driven # by the authenticated agent ingest, not a user-facing privileged action. from steward.core.capabilities import register_capability from steward.models.users import UserRole from .ingest import persist_host_docker from .retention import prune_docker_logs, run_docker_retention register_capability( "docker.persist_host_samples", persist_host_docker, label="Persist host Docker samples", description="Store per-host container state + metrics pushed by the host agent.", required_role=UserRole.viewer, ) # Prune lifecycle events, driven by the core cleanup task without it # importing our models. Same trusted server-side data-plane role as above. # Container CPU and memory live in the one metric store and its retention. register_capability( "docker.run_retention", run_docker_retention, label="Run Docker retention", description="Prune Docker lifecycle events past their window.", required_role=UserRole.viewer, ) register_capability( "docker.prune_logs", prune_docker_logs, label="Prune Docker logs", description="Keep each container's logs within the age window and byte cap.", required_role=UserRole.viewer, ) # Each container's CPU (100% is one core) and memory share learn their # normal (#5742), each with its floor from Settings → Thresholds. Memory # is its processes' own, file cache left out (#6030). from steward.core.norms import floor_setting, learn_normal learn_normal("docker", "cpu_pct", floor_setting(app, "container_cpu_pct")) learn_normal("docker", "mem_used_pct", floor_setting(app, "container_mem_pct")) # A host's agent carries its containers' figures: they go silent with it (#5721). from steward.core.alerts import agent_fed agent_fed("docker", lambda resource: resource.split("/", 1)[0]) def get_scheduled_tasks() -> list: # Collection is now agent-driven (pushed to the host_agent ingest); the # central socket scrape was removed. No periodic task to register. return [] def get_blueprint(): from .routes import docker_bp return docker_bp def register_mcp_tools(register) -> None: from .mcp_tools import register_tools register_tools(register) async def mcp_overview(db) -> list[dict]: from .mcp_tools import mcp_overview as findings return await findings(db) async def mcp_events(db, host_id: str, host_name: str, since, until) -> list[dict]: from .mcp_tools import mcp_events as events return await events(db, host_id, host_name, since, until) def get_nav() -> list[dict]: # Surfaces in the sidebar's Infrastructure group (fleet-wide container view). return [{"label": "Docker", "href": "/plugins/docker/"}] def get_alert_metrics() -> dict[str, list[str]]: """What ingest.py records per container (resource "/"): restart_count is cumulative (alert on crash-looping); is_healthy is 1.0 / 0.0 from the container's HEALTHCHECK, only for containers that define one. Per host (resource ""), what Docker keeps on its disk (#5304). Swarm-wide (resource "swarm"), from any manager's report: services short of their replicas, nodes down and managers unreachable, drained nodes aside (#5788, swarm_health.py).""" from .disk_view import DISK_METRICS from .swarm_health import SWARM_METRICS return {"docker": ["cpu_pct", "mem_used_pct", "restart_count", "is_healthy", *DISK_METRICS, *SWARM_METRICS]}