From decd64b876e5de7beaea6663d3975fa937cc13f4 Mon Sep 17 00:00:00 2001 From: Christian Manivong Date: Sat, 17 Oct 2026 09:20:28 +0200 Subject: [PATCH] feat!: report the package proxy as a fact; no warnings of its own (0.9.0) get_device_warnings decided two things for the application: that any pending update is a warning (it re-read the updates for that), and whether apt's proxy is the one the application's setting wants, which it was handed as optional_args["apt_proxy_url"]. The driver reports facts instead (napalm-device-types 4.14): get_package_proxy() says which proxy apt fetches through, None for none or on a non-apt host; the updates are get_available_updates() as before. get_device_warnings and the apt_proxy_url argument go. BREAKING CHANGE: get_device_warnings and optional_args["apt_proxy_url"] are removed. Requires napalm-device-types 4.14. Closes #28 --- CHANGELOG.md | 6 +++++ napalm_linux/linux.py | 41 ++++++-------------------------- pyproject.toml | 4 ++-- tests/test_fix_templates.py | 23 ++++++++++++++++++ tests/test_staged_root_writes.py | 2 +- 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4735c06..63c7904 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed +- **Breaking:** `get_device_warnings` is gone, and with it the `apt_proxy_url` argument. + The driver reports facts and the application names warnings (napalm-device-types 4.14): + `get_package_proxy()` says which proxy apt fetches through; pending updates were always + in `get_available_updates()`. Requires napalm-device-types 4.14.0; version 0.9.0. + ### Changed - **Breaking:** `run_device_action` is gone, with `fix_snmp`, `fix_apt_proxy`, diff --git a/napalm_linux/linux.py b/napalm_linux/linux.py index b870940..17ada90 100644 --- a/napalm_linux/linux.py +++ b/napalm_linux/linux.py @@ -346,8 +346,6 @@ class LinuxDriver( # Optional sudo password for privilege escalation (e.g. apt-get update) self._sudo_password: str | None = optional_args.get("sudo_password") # Expected apt proxy URL — checked as a device warning on apt systems. - # Only set when apt_proxy_enabled=true in NetOrk settings; empty string disables the check. - self._apt_proxy_url: str = optional_args.get("apt_proxy_url", "") if optional_args.get("debugging"): logger.setLevel(logging.DEBUG) @@ -1510,38 +1508,13 @@ class LinuxDriver( f"Package manager '{self._pkg_manager}' is not supported" ) - def get_device_warnings(self) -> List[dict[str, Any]]: - """Return warning dicts for issues detected on this device. - - Currently detects: - - package updates available (uses local package cache) - - apt proxy not configured (apt systems only) - """ - warnings: List[dict[str, Any]] = [] - try: - updates = self.get_available_updates() - except Exception as exc: - logger.warning("get_device_warnings: get_available_updates() failed: %s", exc) - updates = [] - if updates: - warnings.append({ - "code": "updates_available", - "meta": { - "count": len(updates), - "packages": [u.get("name", "") for u in updates], - }, - }) - if self._pkg_manager == "apt" and self._apt_proxy_url: - try: - current = self._send("cat /etc/apt/apt.conf.d/00proxy 2>/dev/null || true").strip() - if self._apt_proxy_url not in current: - warnings.append({ - "code": "apt_proxy_missing", - "meta": {"expected_url": self._apt_proxy_url}, - }) - except Exception as exc: - logger.warning("get_device_warnings: apt proxy check failed: %s", exc) - return warnings + def get_package_proxy(self) -> str | None: + """The proxy apt fetches through (``00proxy``); None for none or a non-apt host.""" + if self._pkg_manager != "apt": + return None + conf = self._send(f"cat {_APT_PROXY_CONF} 2>/dev/null || true") + found = re.search(r'Acquire::http::Proxy\s+"([^"]+)"', conf) + return found.group(1) if found else None def _get_updates_apt(self) -> list[UpdateDict]: # apt list --upgradable does not need root; avoid sudo so it works even diff --git a/pyproject.toml b/pyproject.toml index 4d0f8bf..39fadae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "napalm-linux" -version = "0.8.0" +version = "0.9.0" description = "NAPALM driver for generic Linux systems via SSH" readme = "README.md" requires-python = ">=3.9" @@ -37,7 +37,7 @@ classifiers = [ ] dependencies = [ "napalm>=4.0", - "napalm-device-types>=4.13.0", + "napalm-device-types>=4.14.0", "netmiko>=4.0.0", "paramiko>=5.0.0", # CVE-2026-44405 ] diff --git a/tests/test_fix_templates.py b/tests/test_fix_templates.py index 7adfe74..51e820c 100644 --- a/tests/test_fix_templates.py +++ b/tests/test_fix_templates.py @@ -211,3 +211,26 @@ class TestManagementPeer: @pytest.mark.parametrize("answer", ["", "\n", "garbage", "not-an-ip 1 2 3"]) def test_nothing_usable_is_none(self, answer): assert self._driver(answer).get_management_peer() is None + + +class TestFactsNotWarnings: + """The driver reports facts; the application names warnings (#28, netOrk #1111).""" + + def test_no_warnings_of_its_own(self): + assert "get_device_warnings" not in vars(LinuxDriver) + + def test_the_package_proxy_apt_fetches_through(self, driver): + driver._send = lambda command, read_timeout=100: 'Acquire::http::Proxy "http://c:3142";' + assert driver.get_package_proxy() == "http://c:3142" + + def test_no_proxy_set(self, driver): + driver._send = lambda command, read_timeout=100: "" + assert driver.get_package_proxy() is None + + def test_not_an_apt_host(self, driver): + driver._pkg_manager = "dnf" + assert driver.get_package_proxy() is None + + def test_the_applications_proxy_url_is_no_argument_any_more(self): + d = LinuxDriver("h", "admin", "p", optional_args={"apt_proxy_url": "http://c:3142"}) + assert not hasattr(d, "_apt_proxy_url") diff --git a/tests/test_staged_root_writes.py b/tests/test_staged_root_writes.py index ae3eec1..ddeecbb 100644 --- a/tests/test_staged_root_writes.py +++ b/tests/test_staged_root_writes.py @@ -123,7 +123,7 @@ def _driver(host: Host) -> LinuxDriver: "h", "admin", "p", - optional_args={"sudo_password": _PW, "apt_proxy_url": "http://proxy:3142"}, + optional_args={"sudo_password": _PW}, ) driver._device = MagicMock() driver._device.send_command.side_effect = _shell -- 2.54.0