From a4f642402bf0a637b27d959a83b1dd4dca66e891 Mon Sep 17 00:00:00 2001 From: Abel Luck Date: Fri, 27 Feb 2026 16:45:20 +0100 Subject: [PATCH] require effective config for slots ttl --- agent/nix_builder_autoscaler/cli.py | 26 +++++++++---------- .../nix_builder_autoscaler/tests/test_cli.py | 20 +++++++++++++- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/agent/nix_builder_autoscaler/cli.py b/agent/nix_builder_autoscaler/cli.py index a084273..f19bca1 100644 --- a/agent/nix_builder_autoscaler/cli.py +++ b/agent/nix_builder_autoscaler/cli.py @@ -96,10 +96,7 @@ def _slot_age_seconds(slot: dict[str, Any]) -> float | None: return (datetime.now(UTC) - dt).total_seconds() -def _slot_ttl(slot: dict[str, Any], policy: dict[str, Any] | None, active_slots: int) -> str: - if policy is None: - return "-" - +def _slot_ttl(slot: dict[str, Any], policy: dict[str, Any], active_slots: int) -> str: capacity = policy.get("capacity") scheduler = policy.get("scheduler") if not isinstance(capacity, dict) or not isinstance(scheduler, dict): @@ -138,7 +135,7 @@ def _slot_ttl(slot: dict[str, Any], policy: dict[str, Any] | None, active_slots: return "-" -def _print_slots(data: list[dict[str, Any]], policy: dict[str, Any] | None = None) -> None: +def _print_slots(data: list[dict[str, Any]], policy: dict[str, Any]) -> None: active_slots = sum(1 for slot in data if str(slot.get("state", "")) not in {"empty", "error"}) rows: list[list[str]] = [] for slot in data: @@ -193,16 +190,15 @@ def _print_status_summary(data: dict[str, Any]) -> None: _print_table(["metric", "value"], rows) -def _get_effective_config(socket_path: str) -> dict[str, Any] | None: - try: - status, data = _uds_request(socket_path, "GET", "/v1/config/effective") - except OSError: - return None +def _get_effective_config(socket_path: str) -> dict[str, Any]: + status, data = _uds_request(socket_path, "GET", "/v1/config/effective") if status < 200 or status >= 300: - return None + msg = "failed to fetch effective config" + raise RuntimeError(msg) if isinstance(data, dict): return data - return None + msg = "invalid effective config payload" + raise RuntimeError(msg) def _bulk_slot_action(socket_path: str, action: str) -> dict[str, Any]: @@ -395,7 +391,11 @@ def main() -> None: print(json.dumps(data, indent=2)) elif args.command == "slots": if isinstance(data, list): - policy = _get_effective_config(args.socket) + try: + policy = _get_effective_config(args.socket) + except RuntimeError as err: + print(str(err)) + raise SystemExit(1) from err _print_slots(data, policy) else: _print_error(data) diff --git a/agent/nix_builder_autoscaler/tests/test_cli.py b/agent/nix_builder_autoscaler/tests/test_cli.py index caa11e8..f3fa6ac 100644 --- a/agent/nix_builder_autoscaler/tests/test_cli.py +++ b/agent/nix_builder_autoscaler/tests/test_cli.py @@ -7,7 +7,13 @@ from datetime import UTC, datetime, timedelta import pytest from nix_builder_autoscaler import cli -from nix_builder_autoscaler.cli import _parse_args, _print_slots, _print_status_summary, _slot_ttl +from nix_builder_autoscaler.cli import ( + _get_effective_config, + _parse_args, + _print_slots, + _print_status_summary, + _slot_ttl, +) def test_parse_args_without_command_prints_help_and_exits_zero( @@ -149,3 +155,15 @@ def test_print_slots_includes_ttl_column(capsys: pytest.CaptureFixture[str]) -> out = capsys.readouterr().out assert "ttl" in out assert "slot001" in out + + +def test_get_effective_config_raises_on_non_2xx(monkeypatch: pytest.MonkeyPatch) -> None: + def _fake_request(socket_path: str, method: str, path: str, body=None): # noqa: ANN001 + assert socket_path == "/tmp/sock" + assert method == "GET" + assert path == "/v1/config/effective" + return 404, {"error": "not found"} + + monkeypatch.setattr(cli, "_uds_request", _fake_request) + with pytest.raises(RuntimeError, match="failed to fetch effective config"): + _get_effective_config("/tmp/sock")