From 07dcdbfe502e7369747f37a241e724fe2c7a8000 Mon Sep 17 00:00:00 2001 From: Christian Manivong Date: Wed, 8 Jul 2026 17:31:16 +0200 Subject: [PATCH] fix(linux): apt_update_upgrade left dependency-driven updates pending MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plain "apt-get upgrade" refuses to install or remove packages even when a newer version requires it, silently holding those updates back — switched to "apt-get full-upgrade" so VM-provisioning bootstrap actually finishes with nothing left to update. --- napalm_linux/linux.py | 9 +++++++-- tests/test_linux.py | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/napalm_linux/linux.py b/napalm_linux/linux.py index d1a760a..6f23b5f 100644 --- a/napalm_linux/linux.py +++ b/napalm_linux/linux.py @@ -1889,7 +1889,12 @@ class LinuxDriver(OSDriver): raise NotImplementedError(f"Unknown action: {action!r}") def _action_apt_update_upgrade(self) -> DeviceActionResultDict: - """Refresh the apt cache and upgrade all packages (apt-based systems only).""" + """Refresh the apt cache and fully upgrade all packages (apt-based systems only). + + Uses full-upgrade (not plain upgrade) — plain "apt-get upgrade" refuses + to install/remove packages even when required to satisfy a newer + version's dependencies, silently leaving those updates pending. + """ if self._pkg_manager != "apt": return { "success": True, @@ -1913,7 +1918,7 @@ class LinuxDriver(OSDriver): out = self._sudo("apt-get update -y 2>&1", read_timeout=90) lines.append(f"[update] {out.strip()[-300:]}") out = self._sudo( - "DEBIAN_FRONTEND=noninteractive apt-get upgrade -y 2>&1", read_timeout=240 + "DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y 2>&1", read_timeout=240 ) lines.append(f"[upgrade] {out.strip()[-300:]}") return {"success": True, "output": "\n".join(lines)} diff --git a/tests/test_linux.py b/tests/test_linux.py index 3057d3c..dec380c 100644 --- a/tests/test_linux.py +++ b/tests/test_linux.py @@ -776,7 +776,9 @@ class TestActionAptUpdateUpgrade: assert "[upgrade]" in result["output"] update_call, upgrade_call = mock_sudo.call_args_list assert "apt-get update" in update_call.args[0] - assert "apt-get upgrade" in upgrade_call.args[0] + # full-upgrade (not plain upgrade) — plain upgrade silently holds back + # packages whose newer version needs to install/remove dependencies. + assert "apt-get full-upgrade" in upgrade_call.args[0] def test_exception_during_upgrade_returns_failure(self, driver): driver._pkg_manager = "apt"