From 2ca6533666d536a9fca09c3a45488d299dff7006 Mon Sep 17 00:00:00 2001 From: anti Date: Thu, 18 Jun 2026 21:24:00 -0400 Subject: [PATCH] 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--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. --- decnet/engine/deployer.py | 13 ++++++++++++- tests/topology/test_deploy.py | 21 ++++++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/decnet/engine/deployer.py b/decnet/engine/deployer.py index fe60a6fb..884c18ca 100644 --- a/decnet/engine/deployer.py +++ b/decnet/engine/deployer.py @@ -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: diff --git a/tests/topology/test_deploy.py b/tests/topology/test_deploy.py index 44cb2272..c95e171d 100644 --- a/tests/topology/test_deploy.py +++ b/tests/topology/test_deploy.py @@ -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