Some checks failed
Deploy server-up (dev) / deploy (push) Has been cancelled
api_stacks sloeg elke map zonder compose-bestand over met een kale continue. Zo'n map dook daarna nergens meer op: niet in de lijst, en de containers die erbij horen belandden onder "handmatig gestart" omdat beheerd op datzelfde compose-bestand afgaat. De interface heeft er wel een weergave voor - rode stip, streepje in plaats van een status, geen knoppen - die door dat continue nooit te zien kon zijn. Ze staan er nu tussen met die waarschuwing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Q9eqpADJSRs49SoGGr4NAy
151 lines
6.2 KiB
Python
151 lines
6.2 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
|
|
|
|
|
|
# ── Een map zonder compose-bestand ───────────────────────────────────────────
|
|
# Die werd stilzwijgend overgeslagen: niet in de lijst, en de containers die
|
|
# erbij hoorden belandden onder "handmatig gestart". Je zag een leeg scherm en
|
|
# nergens stond waarom.
|
|
|
|
def test_map_zonder_compose_verdwijnt_niet_uit_de_lijst(client, env):
|
|
from conftest import login
|
|
login(client)
|
|
(env["lib"] / "kapotte-app").mkdir(parents=True, exist_ok=True)
|
|
|
|
d = client.get("/api/stacks").get_json()
|
|
namen = [s["name"] for s in d]
|
|
assert "kapotte-app" in namen, f"de map is niet zichtbaar: {namen}"
|
|
s = next(s for s in d if s["name"] == "kapotte-app")
|
|
assert s["has_compose"] is False, "hij doet alsof er compose is"
|
|
assert s["containers"] == []
|
|
assert s["running"] is False
|
|
|
|
|
|
def test_een_gewone_app_blijft_gewoon_staan(client, env, monkeypatch):
|
|
from conftest import login
|
|
from core import docker
|
|
monkeypatch.setattr(docker, "compose_ps", lambda *a, **k: [])
|
|
login(client)
|
|
d = env["lib"] / "echte-app"
|
|
d.mkdir(parents=True, exist_ok=True)
|
|
(d / "docker-compose.yml").write_text("services:\n web:\n image: nginx\n")
|
|
|
|
uit = client.get("/api/stacks").get_json()
|
|
s = next(s for s in uit if s["name"] == "echte-app")
|
|
assert s["has_compose"] is True
|