fix(topology): anchor compose path to run dir, stop install-dir litter

_topology_compose_path returned a CWD-relative Path, so every
deploy/mutate/dry-run wrote decnet-topology-<id8>-compose.yml into the
process CWD (the install dir). Teardown computed the same relative path
against its own CWD, so when it differed the unlink() missed the orphan
and files accumulated forever.

Anchor to $DECNET_RUN_DIR (default /var/lib/decnet/topologies, tempdir
fallback) so write and teardown always agree regardless of CWD.
This commit is contained in:
2026-06-18 21:24:00 -04:00
parent bf66e875a5
commit 2ca6533666
2 changed files with 32 additions and 2 deletions

View File

@@ -5,8 +5,10 @@ Deploy, teardown, and status via Docker SDK + subprocess docker compose.
import asyncio
import json
import os
import shutil
import subprocess # nosec B404
import tempfile
import time
from pathlib import Path
from typing import cast
@@ -833,7 +835,16 @@ def _teardown_order(lans: list[dict]) -> list[str]:
def _topology_compose_path(topology_id: str) -> Path:
return Path(f"decnet-topology-{topology_id[:8]}-compose.yml")
# Anchor to a stable absolute dir so write and teardown agree
# regardless of process CWD — a CWD-relative path littered the
# install tree and let teardown's unlink() miss orphaned files.
base = Path(os.environ.get("DECNET_RUN_DIR", "/var/lib/decnet/topologies"))
try:
base.mkdir(parents=True, exist_ok=True)
except OSError:
base = Path(tempfile.gettempdir()) / "decnet-topologies"
base.mkdir(parents=True, exist_ok=True)
return base / f"decnet-topology-{topology_id[:8]}-compose.yml"
def _topology_compose_project(topology_id: str) -> str:

View File

@@ -51,7 +51,7 @@ async def repo(tmp_path):
@pytest.mark.anyio
async def test_dry_run_writes_compose_and_preserves_pending(repo, tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
monkeypatch.setenv("DECNET_RUN_DIR", str(tmp_path))
plan = generate(_cfg())
tid = await persist(repo, plan)
@@ -235,3 +235,22 @@ async def test_deploy_and_teardown_against_real_docker(repo, tmp_path, monkeypat
p.unlink()
# Sanity: Path roundtrip still resolvable
assert isinstance(Path(str(p)), Path)
def test_compose_path_is_absolute_and_cwd_independent(tmp_path, monkeypatch):
"""Regression: a CWD-relative compose path littered the install dir and
let teardown's unlink() miss orphans. Path must be absolute and stable
across CWD changes so write and teardown always agree."""
monkeypatch.setenv("DECNET_RUN_DIR", str(tmp_path))
tid = "abcdef1234567890"
monkeypatch.chdir(tmp_path)
p1 = _topology_compose_path(tid)
sub = tmp_path / "elsewhere"
sub.mkdir()
monkeypatch.chdir(sub)
p2 = _topology_compose_path(tid)
assert p1.is_absolute()
assert p1 == p2, "compose path must not depend on process CWD"
assert p1.parent == tmp_path