feat: apt's proxy through AptProxyMixin (NetOrk/netork#1397)
CI / test (3.10) (push) Failing after 21s
CI / test (3.11) (push) Failing after 19s
CI / test (3.12) (push) Failing after 18s
CI / test (3.10) (pull_request) Failing after 19s
CI / test (3.11) (pull_request) Failing after 17s
CI / test (3.12) (pull_request) Failing after 16s

get_package_proxy() is what apt-config dump says, not 00proxy alone: a
proxy in 90curtin-aptproxy or /etc/apt/apt.conf wins over it and was
missed. The driver now supplies only the transports: reads on the exec
channel, root commands through _root_outcome, the file through the staged
_install_file_as_root. New: get_package_proxy_report() and
check_package_proxy(). configure_package_proxy() also comments out other
proxy lines, reads the result back and fetches through the proxy.

Requires napalm-device-types 4.22.0; version 0.14.0.
This commit is contained in:
Christian Manivong committed 2026-10-22 19:16:40 +02:00
1 parent 0ba5081824
commit 04f071afd0
4 files changed
+93 -31

No files matched your search

+17 -26
View File
@@ -46,7 +46,7 @@ from napalm_device_types import (
ListeningSocketsMixin,
OpenSshServerMixin,
OSDriver,
PackageProxyMixin,
AptProxyMixin,
ServiceClustersMixin,
ServicePackagesMixin,
SnmpAgentMixin,
@@ -261,8 +261,6 @@ _SNMP_INSTALL = {
"apk": "apk add --no-cache net-snmp net-snmp-tools 2>&1",
"pacman": "pacman -Sy --noconfirm net-snmp 2>&1",
}
_APT_PROXY_CONF = "/etc/apt/apt.conf.d/00proxy"
_PROXY_URL = re.compile(r"^https?://[A-Za-z0-9.\-\[\]:@%_~/]+$")
_USER_NAME = re.compile(r"^[a-z_][a-z0-9_.\-]{0,31}$")
#: What may stand in an snmpd.conf value: no whitespace, no quote, no comment.
_SNMP_WORD = re.compile(r"^[^\s\"#]+$")
@@ -294,7 +292,7 @@ def render_snmpd_conf(settings: SnmpAgentSettingsDict) -> str:
class LinuxDriver(
SnmpAgentMixin,
EnsurePackageMixin,
PackageProxyMixin,
AptProxyMixin,
KernelFactsMixin,
ServicePackagesMixin,
ListeningSocketsMixin,
@@ -1544,14 +1542,6 @@ class LinuxDriver(
f"Package manager '{self._pkg_manager}' is not supported"
)
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
# without a configured sudo password.
@@ -2052,22 +2042,23 @@ class LinuxDriver(
"snmpd enabled and running",
)
def configure_package_proxy(self, url: str) -> FixResultDict:
"""apt fetches through *url* (``/etc/apt/apt.conf.d/00proxy``); ``""`` removes it."""
# apt's proxy (napalm-device-types AptProxyMixin): the transports
def write() -> FixStepOutcomeDict:
if self._pkg_manager != "apt":
return {"ok": False, "output": f"package manager is {self._pkg_manager!r}, not apt"}
if not url:
return self._root_outcome(f"rm -f {_APT_PROXY_CONF}", 30, "proxy removed")
if not _PROXY_URL.match(url):
raise ValueError(f"not a proxy URL: {url!r}")
written = self._install_file_as_root(_APT_PROXY_CONF, f'Acquire::http::Proxy "{url}";\n')
if written.exit_code:
return {"ok": False, "output": (written.stdout or written.stderr).strip()[-300:]}
return {"ok": True, "output": f"apt fetches through {url}"}
def _is_apt_host(self) -> bool:
return self._pkg_manager == "apt"
return run_fix_steps([("configure", write)])
def _run_apt_command(self, command: str) -> str:
"""On the exec channel: no prompt to mistake a line of apt's output for."""
return self.run_command(command, timeout=60).stdout
def _run_apt_root_command(self, command: str, done: str) -> FixStepOutcomeDict:
return self._root_outcome(command, 30, done)
def _write_apt_file(self, path: str, content: str) -> FixStepOutcomeDict:
written = self._install_file_as_root(path, content)
if written.exit_code:
return {"ok": False, "output": (written.stdout or written.stderr).strip()[-300:]}
return {"ok": True, "output": f"wrote {path}"}
def grant_container_engine_access(self, user: str) -> FixResultDict:
"""Add *user* to the ``docker`` group, so the engine answers without sudo."""