fix(host): derive template backup dir from config, drop hardcoded path (W2)
backup_template used a hardcoded _BACKUP_BASE_DIR=/var/lib/ci/backups; now derives <config.paths.root>/backups (matches the CLI --backup-base-dir default) per the plan's no-hardcoded-path gotcha. Tests no longer need to monkeypatch the constant.
This commit is contained in:
@@ -20,7 +20,7 @@ Stato passi (spunta man mano):
|
|||||||
|
|
||||||
- [x] **W0** — Scaffolding: extra `[web]` in `pyproject.toml`, dir package `operations/` + `web/`, skeleton test. Nessuna logica.
|
- [x] **W0** — Scaffolding: extra `[web]` in `pyproject.toml`, dir package `operations/` + `web/`, skeleton test. Nessuna logica.
|
||||||
- [x] **W1** — Layer `operations/vms.py`: estrai la logica da `commands/vm.py` in funzioni tipate che ritornano dataclass. Refactor dei comandi CLI per chiamarle. **Comportamento CLI invariato.** (Incluso: seam `VmBackend.list_running()` + fold del fix §2.8 IP-pool leak via `IpSlotPool.reconcile` / `operations.vms.reconcile_ip_leases`.)
|
- [x] **W1** — Layer `operations/vms.py`: estrai la logica da `commands/vm.py` in funzioni tipate che ritornano dataclass. Refactor dei comandi CLI per chiamarle. **Comportamento CLI invariato.** (Incluso: seam `VmBackend.list_running()` + fold del fix §2.8 IP-pool leak via `IpSlotPool.reconcile` / `operations.vms.reconcile_ip_leases`.)
|
||||||
- [ ] **W2** — Layer `operations/cache.py` + `operations/host.py`: stato cache cargo, purge, disco, runner, backup template. Refactor `retention.py` / `monitor.py`.
|
- [x] **W2** — Layer `operations/cache.py` + `operations/host.py`: stato cache cargo, purge, disco, runner, backup template. Refactor `retention.py` (`monitor.py` lasciato invariato — refactor opzionale; `disk_status`/`runner_status` verranno consumati dalla web app in W4).
|
||||||
- [ ] **W3** — `operations/audit.py`: append-only JSONL + lettura. Config `[web]` in `config.py`.
|
- [ ] **W3** — `operations/audit.py`: append-only JSONL + lettura. Config `[web]` in `config.py`.
|
||||||
- [ ] **W4** — App web core: `web/app.py` factory + `commands/web.py` (`ci web serve`). Dashboard **read-only** (VM, cache, disco, runner). Ancora **senza auth**, bind localhost.
|
- [ ] **W4** — App web core: `web/app.py` factory + `commands/web.py` (`ci web serve`). Dashboard **read-only** (VM, cache, disco, runner). Ancora **senza auth**, bind localhost.
|
||||||
- [ ] **W5** — Auth: Gitea OIDC (Authlib) + sessione firmata + allowlist utenti. Tutte le route protette.
|
- [ ] **W5** — Auth: Gitea OIDC (Authlib) + sessione firmata + allowlist utenti. Tutte le route protette.
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ every ``systemctl`` invocation is an argument list, read-only.
|
|||||||
|
|
||||||
``backup_template`` reuses the archive-step shape from
|
``backup_template`` reuses the archive-step shape from
|
||||||
``commands/template.py::template_backup`` (timestamped ``7z`` archive under
|
``commands/template.py::template_backup`` (timestamped ``7z`` archive under
|
||||||
``/var/lib/ci/backups``) but is a fresh, click-free implementation so this
|
``<config.paths.root>/backups``) but is a fresh, click-free implementation so
|
||||||
module has no dependency on the CLI layer.
|
this module has no dependency on the CLI layer.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -27,7 +27,6 @@ if TYPE_CHECKING: # pragma: no cover
|
|||||||
from ci_orchestrator.config import Config
|
from ci_orchestrator.config import Config
|
||||||
|
|
||||||
_SYSTEMCTL_TIMEOUT = 10.0
|
_SYSTEMCTL_TIMEOUT = 10.0
|
||||||
_BACKUP_BASE_DIR = Path("/var/lib/ci/backups")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@@ -141,14 +140,17 @@ def backup_template(config: Config, template: str, *, dry_run: bool = False) ->
|
|||||||
error=f"Template directory not found: {tmpl_dir}",
|
error=f"Template directory not found: {tmpl_dir}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Backup dir is config-driven (mirrors the CLI's --backup-base-dir default
|
||||||
|
# of <root>/backups); no hardcoded host path.
|
||||||
|
backup_base = config.paths.root / "backups"
|
||||||
timestamp = datetime.now(tz=UTC).strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.now(tz=UTC).strftime("%Y%m%d_%H%M%S")
|
||||||
archive_path = _BACKUP_BASE_DIR / f"Template_{template}_{timestamp}.7z"
|
archive_path = backup_base / f"Template_{template}_{timestamp}.7z"
|
||||||
|
|
||||||
if dry_run:
|
if dry_run:
|
||||||
return BackupResult(template=template, archive_path=archive_path, ok=True, error=None)
|
return BackupResult(template=template, archive_path=archive_path, ok=True, error=None)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_BACKUP_BASE_DIR.mkdir(parents=True, exist_ok=True)
|
backup_base.mkdir(parents=True, exist_ok=True)
|
||||||
_compress_7z(tmpl_dir, archive_path)
|
_compress_7z(tmpl_dir, archive_path)
|
||||||
except (OSError, subprocess.CalledProcessError) as exc:
|
except (OSError, subprocess.CalledProcessError) as exc:
|
||||||
return BackupResult(template=template, archive_path=None, ok=False, error=str(exc))
|
return BackupResult(template=template, archive_path=None, ok=False, error=str(exc))
|
||||||
|
|||||||
@@ -203,7 +203,6 @@ def test_backup_template_dry_run_does_not_compress(monkeypatch: pytest.MonkeyPat
|
|||||||
tmpl_dir = templates / "WinBuild2025"
|
tmpl_dir = templates / "WinBuild2025"
|
||||||
tmpl_dir.mkdir(parents=True)
|
tmpl_dir.mkdir(parents=True)
|
||||||
config = _make_config(templates)
|
config = _make_config(templates)
|
||||||
monkeypatch.setattr(host_module, "_BACKUP_BASE_DIR", tmp_path / "backups")
|
|
||||||
|
|
||||||
calls: list[tuple[Path, Path]] = []
|
calls: list[tuple[Path, Path]] = []
|
||||||
monkeypatch.setattr(host_module, "_compress_7z", lambda src, dst: calls.append((src, dst)))
|
monkeypatch.setattr(host_module, "_compress_7z", lambda src, dst: calls.append((src, dst)))
|
||||||
@@ -225,7 +224,6 @@ def test_backup_template_success_mocked_archive_step(
|
|||||||
tmpl_dir = templates / "WinBuild2025"
|
tmpl_dir = templates / "WinBuild2025"
|
||||||
tmpl_dir.mkdir(parents=True)
|
tmpl_dir.mkdir(parents=True)
|
||||||
config = _make_config(templates)
|
config = _make_config(templates)
|
||||||
monkeypatch.setattr(host_module, "_BACKUP_BASE_DIR", tmp_path / "backups")
|
|
||||||
|
|
||||||
def _fake_compress(src: Path, dst: Path) -> None:
|
def _fake_compress(src: Path, dst: Path) -> None:
|
||||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||||
@@ -249,7 +247,6 @@ def test_backup_template_archive_failure_reported(
|
|||||||
tmpl_dir = templates / "WinBuild2025"
|
tmpl_dir = templates / "WinBuild2025"
|
||||||
tmpl_dir.mkdir(parents=True)
|
tmpl_dir.mkdir(parents=True)
|
||||||
config = _make_config(templates)
|
config = _make_config(templates)
|
||||||
monkeypatch.setattr(host_module, "_BACKUP_BASE_DIR", tmp_path / "backups")
|
|
||||||
|
|
||||||
def _boom(_src: Path, _dst: Path) -> None:
|
def _boom(_src: Path, _dst: Path) -> None:
|
||||||
raise subprocess.CalledProcessError(1, ["7z"])
|
raise subprocess.CalledProcessError(1, ["7z"])
|
||||||
@@ -269,7 +266,6 @@ def test_backup_template_never_calls_real_vmrun_or_7z(monkeypatch: pytest.Monkey
|
|||||||
tmpl_dir = templates / "WinBuild2025"
|
tmpl_dir = templates / "WinBuild2025"
|
||||||
tmpl_dir.mkdir(parents=True)
|
tmpl_dir.mkdir(parents=True)
|
||||||
config = _make_config(templates)
|
config = _make_config(templates)
|
||||||
monkeypatch.setattr(host_module, "_BACKUP_BASE_DIR", tmp_path / "backups")
|
|
||||||
|
|
||||||
called: list[bool] = []
|
called: list[bool] = []
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user