server-up/tests/test_containers.py
Ramon 7f0d34cb1a
All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m5s
v0.5.50-beta - containers tonen die niet bij een beheerde stack horen
Het dashboard meldde 13 actieve containers en 17 images terwijl die nergens
terug te vinden waren. De tellers komen van `docker info` (de hele daemon), de
stacklijst toont alleen mappen in LIBRARY_DIR met een compose-bestand. Alles wat
buiten Server Up om gestart is telde dus wel mee maar was onzichtbaar.

- Nieuw /api/containers: alle containers op de host, met per container of hij
  bij een beheerde stack hoort (compose-project of werkmap onder LIBRARY_DIR).
- Blok "Overige containers" onder de stacklijst, met start/stop/herstart/logs.
- Dashboardteller "actief" vermeldt nu hoeveel daarvan via Server Up loopt.
- De eigen container is gemarkeerd en kan niet via dit blok gestopt worden;
  daarvoor blijft de herstartknop in de instellingen.
- Rollen: een viewer ziet de lijst maar mag niets bedienen.

Verholpen tijdens het bouwen: containers zonder poorten en zonder
compose-labels vielen uit de lijst als ze als laatste regel stonden, doordat
strip() de lege velden aan het eind wegnam en de regel daarna te weinig
kolommen leek te hebben.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7oLCRYzY5ixJ5Sv8Y8EFb
2026-07-27 07:57:35 +02:00

118 lines
4.8 KiB
Python

"""Containers op de host: wat is beheerd en wat niet.
Het dashboard telt alles wat de Docker-daemon kent, terwijl de stacklijst alleen
toont wat in LIBRARY_DIR staat. Containers die daarbuiten vallen waren zichtbaar
in de cijfers maar nergens terug te vinden.
"""
import pytest
from conftest import login
PS_UITVOER = "\n".join([
"abc123\tvaultwarden\tvaultwarden/server:latest\trunning\tUp 2 hours\t0.0.0.0:8222->80/tcp\tvaultwarden\t/opt/serverup/stacks/vaultwarden",
"def456\tforgejo\tcodeberg.org/forgejo/forgejo:1.21\trunning\tUp 3 days\t0.0.0.0:3000->3000/tcp\tforgejo\t/opt/docker/forgejo",
"ghi789\tact_runner\tgitea/act_runner:latest\trunning\tUp 3 days\t\t\t",
"jkl012\tserver-up\tserver-up:0.5.46-beta\trunning\tUp 5 minutes\t127.0.0.1:5000->5000/tcp\tserver-up\t/opt/docker/server-up",
"mno345\toud-testje\tnginx:latest\texited\tExited (0) 4 days ago\t\t\t",
])
@pytest.fixture
def host(env, monkeypatch):
from core import docker
monkeypatch.setattr(docker, "_run", lambda *a, **k: type(
"R", (), {"returncode": 0, "stdout": PS_UITVOER, "stderr": ""})())
return docker
def test_list_containers_leest_alle_velden(host):
c = host.list_containers()
assert len(c) == 5
vw = next(x for x in c if x["name"] == "vaultwarden")
assert vw["image"] == "vaultwarden/server:latest"
assert vw["running"] is True
assert vw["project"] == "vaultwarden"
assert vw["working_dir"] == "/opt/serverup/stacks/vaultwarden"
def test_list_containers_zonder_labels(host):
"""Een container zonder compose-labels heeft lege project-/werkmapvelden."""
c = next(x for x in host.list_containers() if x["name"] == "act_runner")
assert c["project"] == "" and c["working_dir"] == ""
assert c["running"] is True
def test_gestopte_container_wordt_herkend(host):
c = next(x for x in host.list_containers() if x["name"] == "oud-testje")
assert c["running"] is False
def test_endpoint_scheidt_beheerd_van_de_rest(client, env, monkeypatch):
from core import docker
login(client)
# Eén stack in de library: vaultwarden.
stack = env["lib"] / "vaultwarden"
stack.mkdir(parents=True)
(stack / "docker-compose.yml").write_text("services:\n app:\n image: nginx\n")
monkeypatch.setattr(docker, "_run", lambda *a, **k: type(
"R", (), {"returncode": 0,
"stdout": PS_UITVOER.replace("/opt/serverup/stacks/vaultwarden",
str(stack)),
"stderr": ""})())
d = client.get("/api/containers").get_json()
beheerd = [c["name"] for c in d["containers"] if c["managed"]]
overig = [c["name"] for c in d["containers"] if not c["managed"]]
assert beheerd == ["vaultwarden"]
assert set(overig) == {"forgejo", "act_runner", "server-up", "oud-testje"}
assert d["managed"] == 1
assert d["total"] == 5
assert d["running"] == 4
def test_eigen_container_wordt_gemarkeerd(client, env, monkeypatch):
from core import docker
import app as app_module
login(client)
monkeypatch.setattr(app_module, "CONTAINER_NAME", "server-up")
monkeypatch.setattr(docker, "_run", lambda *a, **k: type(
"R", (), {"returncode": 0, "stdout": PS_UITVOER, "stderr": ""})())
d = client.get("/api/containers").get_json()
assert next(c for c in d["containers"] if c["name"] == "server-up")["self"] is True
def test_eigen_container_kan_niet_gestopt_worden(client, monkeypatch):
"""Zichzelf stoppen midden in een verzoek levert een halve actie op; daar
is de aparte herstartknop voor."""
import app as app_module
csrf = login(client)
monkeypatch.setattr(app_module, "CONTAINER_NAME", "server-up")
r = client.post("/api/containers/server-up/stop", headers={"X-CSRF-Token": csrf})
assert r.status_code == 400
assert "herstarten" in r.get_json()["msg"]
def test_onbekende_container(client, host):
csrf = login(client)
r = client.post("/api/containers/bestaatniet/stop", headers={"X-CSRF-Token": csrf})
assert r.status_code == 404
def test_onbekende_actie(client, host):
csrf = login(client)
r = client.post("/api/containers/forgejo/verwijder", headers={"X-CSRF-Token": csrf})
assert r.status_code == 400
def test_viewer_mag_geen_containers_bedienen(client, host):
csrf = login(client)
client.post("/api/auth/users",
json={"username": "kijker", "password": "eenlangwachtwoord",
"role": "viewer"},
headers={"X-CSRF-Token": csrf})
r = client.post("/api/auth/login",
json={"username": "kijker", "password": "eenlangwachtwoord"})
kijker_csrf = r.get_json()["csrf_token"]
assert client.get("/api/containers").status_code == 200
assert client.post("/api/containers/forgejo/stop",
headers={"X-CSRF-Token": kijker_csrf}).status_code == 403