All checks were successful
Deploy server-up (dev) / deploy (push) Successful in 1m5s
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
118 lines
4.8 KiB
Python
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
|