style: ruff format and the lint rules of the driver standard
Mechanical: ruff format, and ruff check --fix with the rule set every NAPALM driver uses (E, F, I, UP, B at line length 100, Python 3.10). What ruff cannot fix comes in the next commit.
This commit is contained in:
1 parent
4a95a5f0d4
commit
de00041b46
4 files changed
+42
-28
No files matched your search
+24
-17
@@ -11,22 +11,21 @@ from __future__ import annotations
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import socket
|
|
||||||
from shlex import quote
|
from shlex import quote
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
import paramiko
|
import paramiko
|
||||||
from napalm.base.exceptions import ConnectionClosedException, ConnectionException
|
from napalm.base.exceptions import ConnectionClosedException, ConnectionException
|
||||||
from napalm_device_types import EnsurePackageMixin, OSDriver, SnmpAgentMixin
|
from napalm_device_types import EnsurePackageMixin, OSDriver, SnmpAgentMixin
|
||||||
from napalm_device_types.models import FixStepOutcomeDict, SnmpAgentSettingsDict
|
|
||||||
from napalm_device_types.host_status import HostStatusMixin
|
|
||||||
from napalm_device_types.ssh_host_keys import HostKeyCheck, SshHostKeyMismatch
|
|
||||||
from napalm_device_types.channel import (
|
from napalm_device_types.channel import (
|
||||||
ByteStream,
|
ByteStream,
|
||||||
CommandResult,
|
CommandResult,
|
||||||
open_stream_on_transport,
|
open_stream_on_transport,
|
||||||
run_on_transport,
|
run_on_transport,
|
||||||
)
|
)
|
||||||
|
from napalm_device_types.host_status import HostStatusMixin
|
||||||
|
from napalm_device_types.models import FixStepOutcomeDict, SnmpAgentSettingsDict
|
||||||
|
from napalm_device_types.ssh_host_keys import HostKeyCheck, SshHostKeyMismatch
|
||||||
|
|
||||||
from napalm_bsd import parse
|
from napalm_bsd import parse
|
||||||
|
|
||||||
@@ -63,6 +62,7 @@ _STAGE = (
|
|||||||
'{ cat > "$t" && printf %s "$t" || { s=$?; rm -f -- "$t"; exit $s; }; }'
|
'{ cat > "$t" && printf %s "$t" || { s=$?; rm -f -- "$t"; exit $s; }; }'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _source(value: str) -> str:
|
def _source(value: str) -> str:
|
||||||
"""An allowed source as an address or network, or ValueError."""
|
"""An allowed source as an address or network, or ValueError."""
|
||||||
return str(ipaddress.ip_network(value.strip(), strict=False))
|
return str(ipaddress.ip_network(value.strip(), strict=False))
|
||||||
@@ -113,7 +113,7 @@ class BsdDriver(SnmpAgentMixin, EnsurePackageMixin, HostStatusMixin, OSDriver):
|
|||||||
username: str,
|
username: str,
|
||||||
password: str,
|
password: str,
|
||||||
timeout: int = 60,
|
timeout: int = 60,
|
||||||
optional_args: Optional[dict] = None,
|
optional_args: dict | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.hostname = hostname
|
self.hostname = hostname
|
||||||
self.username = username
|
self.username = username
|
||||||
@@ -121,14 +121,14 @@ class BsdDriver(SnmpAgentMixin, EnsurePackageMixin, HostStatusMixin, OSDriver):
|
|||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
args = optional_args or {}
|
args = optional_args or {}
|
||||||
self.port: int = int(args.get("port", 22))
|
self.port: int = int(args.get("port", 22))
|
||||||
self._key_file: Optional[str] = args.get("key_file")
|
self._key_file: str | None = args.get("key_file")
|
||||||
self._sudo_password: Optional[str] = args.get("sudo_password")
|
self._sudo_password: str | None = args.get("sudo_password")
|
||||||
self._allow_agent: bool = bool(args.get("allow_agent", False))
|
self._allow_agent: bool = bool(args.get("allow_agent", False))
|
||||||
self._look_for_keys: bool = bool(args.get("look_for_keys", False))
|
self._look_for_keys: bool = bool(args.get("look_for_keys", False))
|
||||||
# The host key to expect, and where the one seen is reported (netork#1167).
|
# The host key to expect, and where the one seen is reported (netork#1167).
|
||||||
self._host_key = HostKeyCheck(args, hostname, self.port)
|
self._host_key = HostKeyCheck(args, hostname, self.port)
|
||||||
self._client: Optional[paramiko.SSHClient] = None
|
self._client: paramiko.SSHClient | None = None
|
||||||
self._root: Optional[bool] = None
|
self._root: bool | None = None
|
||||||
|
|
||||||
# -- connection --------------------------------------------------------------
|
# -- connection --------------------------------------------------------------
|
||||||
|
|
||||||
@@ -147,7 +147,7 @@ class BsdDriver(SnmpAgentMixin, EnsurePackageMixin, HostStatusMixin, OSDriver):
|
|||||||
except SshHostKeyMismatch:
|
except SshHostKeyMismatch:
|
||||||
client.close() # refused before any login; nothing was sent
|
client.close() # refused before any login; nothing was sent
|
||||||
raise
|
raise
|
||||||
except (paramiko.SSHException, socket.error) as exc:
|
except (OSError, paramiko.SSHException) as exc:
|
||||||
client.close()
|
client.close()
|
||||||
raise ConnectionException(f"SSH to {self.hostname} failed: {exc}") from exc
|
raise ConnectionException(f"SSH to {self.hostname} failed: {exc}") from exc
|
||||||
self._client = client
|
self._client = client
|
||||||
@@ -175,7 +175,7 @@ class BsdDriver(SnmpAgentMixin, EnsurePackageMixin, HostStatusMixin, OSDriver):
|
|||||||
self._root = run_on_transport(self._transport(), "id -u").stdout.strip() == "0"
|
self._root = run_on_transport(self._transport(), "id -u").stdout.strip() == "0"
|
||||||
return self._root
|
return self._root
|
||||||
|
|
||||||
def _privileged(self, command: str, privileged: bool) -> tuple[str, Optional[bytes]]:
|
def _privileged(self, command: str, privileged: bool) -> tuple[str, bytes | None]:
|
||||||
"""The command line that runs *command* as asked, and what goes to stdin first.
|
"""The command line that runs *command* as asked, and what goes to stdin first.
|
||||||
|
|
||||||
With a sudo password, ``sudo -S`` reads it from stdin, so it never
|
With a sudo password, ``sudo -S`` reads it from stdin, so it never
|
||||||
@@ -194,7 +194,7 @@ class BsdDriver(SnmpAgentMixin, EnsurePackageMixin, HostStatusMixin, OSDriver):
|
|||||||
*,
|
*,
|
||||||
privileged: bool = False,
|
privileged: bool = False,
|
||||||
timeout: float = 60,
|
timeout: float = 60,
|
||||||
stdin: Optional[bytes] = None,
|
stdin: bytes | None = None,
|
||||||
) -> CommandResult:
|
) -> CommandResult:
|
||||||
"""Run *command* on an exec channel: no PTY, stderr apart, a real exit code.
|
"""Run *command* on an exec channel: no PTY, stderr apart, a real exit code.
|
||||||
|
|
||||||
@@ -217,7 +217,11 @@ class BsdDriver(SnmpAgentMixin, EnsurePackageMixin, HostStatusMixin, OSDriver):
|
|||||||
staged = self.run_command(_STAGE, timeout=30, stdin=content.encode())
|
staged = self.run_command(_STAGE, timeout=30, stdin=content.encode())
|
||||||
lines = staged.stdout.strip().splitlines()
|
lines = staged.stdout.strip().splitlines()
|
||||||
tmp = lines[-1] if lines else ""
|
tmp = lines[-1] if lines else ""
|
||||||
if staged.exit_code != 0 or not tmp.startswith(_STAGE_PREFIX) or "/" in tmp[len(_STAGE_PREFIX):]:
|
if (
|
||||||
|
staged.exit_code != 0
|
||||||
|
or not tmp.startswith(_STAGE_PREFIX)
|
||||||
|
or "/" in tmp[len(_STAGE_PREFIX) :]
|
||||||
|
):
|
||||||
reason = (staged.stderr or staged.stdout).strip() or f"could not stage {path}"
|
reason = (staged.stderr or staged.stdout).strip() or f"could not stage {path}"
|
||||||
return CommandResult(reason, "", staged.exit_code or 1)
|
return CommandResult(reason, "", staged.exit_code or 1)
|
||||||
tmp_q, part_q, path_q = (quote(p) for p in (tmp, f"{path}.napalm-tmp", path))
|
tmp_q, part_q, path_q = (quote(p) for p in (tmp, f"{path}.napalm-tmp", path))
|
||||||
@@ -474,7 +478,7 @@ class BsdDriver(SnmpAgentMixin, EnsurePackageMixin, HostStatusMixin, OSDriver):
|
|||||||
#: net-snmp's daemon; OpenBSD's base snmpd is /usr/sbin/snmpd.
|
#: net-snmp's daemon; OpenBSD's base snmpd is /usr/sbin/snmpd.
|
||||||
SNMPD_DAEMON = "/usr/local/sbin/snmpd"
|
SNMPD_DAEMON = "/usr/local/sbin/snmpd"
|
||||||
|
|
||||||
def get_management_peer(self) -> Optional[str]:
|
def get_management_peer(self) -> str | None:
|
||||||
"""The address this host sees the SSH connection come from (``SSH_CONNECTION``)."""
|
"""The address this host sees the SSH connection come from (``SSH_CONNECTION``)."""
|
||||||
out = self.run_command(_PEER_COMMAND, timeout=15).stdout.split()
|
out = self.run_command(_PEER_COMMAND, timeout=15).stdout.split()
|
||||||
try:
|
try:
|
||||||
@@ -488,7 +492,10 @@ class BsdDriver(SnmpAgentMixin, EnsurePackageMixin, HostStatusMixin, OSDriver):
|
|||||||
def _step(self, command: str, done: str) -> FixStepOutcomeDict:
|
def _step(self, command: str, done: str) -> FixStepOutcomeDict:
|
||||||
result = self.run_command(command, privileged=True, timeout=600)
|
result = self.run_command(command, privileged=True, timeout=600)
|
||||||
output = "\n".join(filter(None, (result.stdout.strip(), result.stderr.strip())))
|
output = "\n".join(filter(None, (result.stdout.strip(), result.stderr.strip())))
|
||||||
return {"ok": result.exit_code == 0, "output": done if result.exit_code == 0 else output[-300:]}
|
return {
|
||||||
|
"ok": result.exit_code == 0,
|
||||||
|
"output": done if result.exit_code == 0 else output[-300:],
|
||||||
|
}
|
||||||
|
|
||||||
def _install_snmp_agent(self) -> FixStepOutcomeDict:
|
def _install_snmp_agent(self) -> FixStepOutcomeDict:
|
||||||
return self._step(self.INSTALL_COMMAND.format(name="net-snmp"), "net-snmp installed")
|
return self._step(self.INSTALL_COMMAND.format(name="net-snmp"), "net-snmp installed")
|
||||||
@@ -507,7 +514,7 @@ class BsdDriver(SnmpAgentMixin, EnsurePackageMixin, HostStatusMixin, OSDriver):
|
|||||||
def _start_snmp_agent(self) -> FixStepOutcomeDict:
|
def _start_snmp_agent(self) -> FixStepOutcomeDict:
|
||||||
return self._step(self.SNMPD_START, "snmpd enabled and running")
|
return self._step(self.SNMPD_START, "snmpd enabled and running")
|
||||||
|
|
||||||
def get_snmp_config(self) -> Optional[dict[str, Any]]:
|
def get_snmp_config(self) -> dict[str, Any] | None:
|
||||||
"""net-snmp's community and port, if its daemon runs; None otherwise."""
|
"""net-snmp's community and port, if its daemon runs; None otherwise."""
|
||||||
if not self._out(f"pgrep -f {self.SNMPD_DAEMON}"):
|
if not self._out(f"pgrep -f {self.SNMPD_DAEMON}"):
|
||||||
return None
|
return None
|
||||||
|
|||||||
+8
-8
@@ -9,7 +9,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import re
|
import re
|
||||||
from typing import Any, Optional
|
from typing import Any
|
||||||
|
|
||||||
_IFACE_HEADER = re.compile(
|
_IFACE_HEADER = re.compile(
|
||||||
r"^(?P<name>[^\s:]+): flags=[0-9a-f]+<(?P<flags>[^>]*)>(?:.*?\bmtu (?P<mtu>\d+))?"
|
r"^(?P<name>[^\s:]+): flags=[0-9a-f]+<(?P<flags>[^>]*)>(?:.*?\bmtu (?P<mtu>\d+))?"
|
||||||
@@ -26,7 +26,7 @@ def _prefix_from_netmask(mask: str) -> int:
|
|||||||
def ifconfig(text: str) -> dict[str, dict[str, Any]]:
|
def ifconfig(text: str) -> dict[str, dict[str, Any]]:
|
||||||
"""``ifconfig -a`` -> per interface: flags, MTU, MAC, state, speed, addresses."""
|
"""``ifconfig -a`` -> per interface: flags, MTU, MAC, state, speed, addresses."""
|
||||||
interfaces: dict[str, dict[str, Any]] = {}
|
interfaces: dict[str, dict[str, Any]] = {}
|
||||||
current: Optional[dict[str, Any]] = None
|
current: dict[str, Any] | None = None
|
||||||
for line in text.splitlines():
|
for line in text.splitlines():
|
||||||
header = _IFACE_HEADER.match(line)
|
header = _IFACE_HEADER.match(line)
|
||||||
if header:
|
if header:
|
||||||
@@ -69,7 +69,7 @@ def ifconfig(text: str) -> dict[str, dict[str, Any]]:
|
|||||||
return interfaces
|
return interfaces
|
||||||
|
|
||||||
|
|
||||||
def _network(destination: str, family: str) -> Optional[str]:
|
def _network(destination: str, family: str) -> str | None:
|
||||||
"""A netstat destination as a network: ``default``, ``10.0.2/24``, ``fe80::%em0/64``."""
|
"""A netstat destination as a network: ``default``, ``10.0.2/24``, ``fe80::%em0/64``."""
|
||||||
if destination == "default":
|
if destination == "default":
|
||||||
return "0.0.0.0/0" if family == "ipv4" else "::/0"
|
return "0.0.0.0/0" if family == "ipv4" else "::/0"
|
||||||
@@ -93,7 +93,7 @@ def routes(text: str) -> list[dict[str, str]]:
|
|||||||
"""
|
"""
|
||||||
result: list[dict[str, str]] = []
|
result: list[dict[str, str]] = []
|
||||||
family = ""
|
family = ""
|
||||||
iface_column: Optional[int] = None
|
iface_column: int | None = None
|
||||||
for line in text.splitlines():
|
for line in text.splitlines():
|
||||||
words = line.split()
|
words = line.split()
|
||||||
if not words:
|
if not words:
|
||||||
@@ -253,15 +253,15 @@ def crontab(text: str, *, system: bool = False, user: str = "") -> list[dict[str
|
|||||||
return jobs
|
return jobs
|
||||||
|
|
||||||
|
|
||||||
def boottime(text: str) -> Optional[int]:
|
def boottime(text: str) -> int | None:
|
||||||
"""``sysctl -n kern.boottime``: FreeBSD ``{ sec = N, … }``, OpenBSD ``N``."""
|
"""``sysctl -n kern.boottime``: FreeBSD ``{ sec = N, … }``, OpenBSD ``N``."""
|
||||||
match = re.search(r"sec = (\d+)", text) or re.fullmatch(r"\s*(\d+)\s*", text)
|
match = re.search(r"sec = (\d+)", text) or re.fullmatch(r"\s*(\d+)\s*", text)
|
||||||
return int(match[1]) if match else None
|
return int(match[1]) if match else None
|
||||||
|
|
||||||
|
|
||||||
def _socket(
|
def _socket(
|
||||||
proto: str, local: str, family: str, process: Optional[str], pid: Optional[int]
|
proto: str, local: str, family: str, process: str | None, pid: int | None
|
||||||
) -> Optional[dict[str, Any]]:
|
) -> dict[str, Any] | None:
|
||||||
"""A ListeningSocketDict from a local endpoint: ``*:22``, ``[::1]:25``,
|
"""A ListeningSocketDict from a local endpoint: ``*:22``, ``[::1]:25``,
|
||||||
``127.0.0.1:25``, ``[fe80::1%lo0]:25`` (sockstat, fstat) or ``*.22``,
|
``127.0.0.1:25``, ``[fe80::1%lo0]:25`` (sockstat, fstat) or ``*.22``,
|
||||||
``::1.25``, ``fe80::1%lo0.25`` (netstat)."""
|
``::1.25``, ``fe80::1%lo0.25`` (netstat)."""
|
||||||
@@ -288,7 +288,7 @@ def _socket(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _unique(sockets: list[Optional[dict[str, Any]]]) -> list[dict[str, Any]]:
|
def _unique(sockets: list[dict[str, Any] | None]) -> list[dict[str, Any]]:
|
||||||
"""One entry per socket, the first process holding it, as ss reports it."""
|
"""One entry per socket, the first process holding it, as ss reports it."""
|
||||||
seen: set[tuple] = set()
|
seen: set[tuple] = set()
|
||||||
result = []
|
result = []
|
||||||
|
|||||||
@@ -47,3 +47,10 @@ include = ["napalm_bsd*"]
|
|||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
line-length = 100
|
line-length = 100
|
||||||
target-version = "py310"
|
target-version = "py310"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = ["E", "F", "I", "UP", "B"]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
# Recorded device output keeps its lines.
|
||||||
|
"tests/**" = ["E501"]
|
||||||
@@ -8,8 +8,7 @@ import pytest
|
|||||||
from napalm.base import NetworkDriver
|
from napalm.base import NetworkDriver
|
||||||
from napalm_device_types import OSDriver
|
from napalm_device_types import OSDriver
|
||||||
|
|
||||||
from napalm_bsd import FreeBSDDriver, OpenBSDDriver
|
from napalm_bsd import FreeBSDDriver, OpenBSDDriver, base
|
||||||
from napalm_bsd import base
|
|
||||||
from napalm_bsd.base import BsdDriver
|
from napalm_bsd.base import BsdDriver
|
||||||
|
|
||||||
FIXTURES = Path(__file__).parent / "fixtures"
|
FIXTURES = Path(__file__).parent / "fixtures"
|
||||||
@@ -600,7 +599,8 @@ class TestPrivilegedStdin:
|
|||||||
def _driver(self, monkeypatch, *, sudo_password, root=False):
|
def _driver(self, monkeypatch, *, sudo_password, root=False):
|
||||||
sent = []
|
sent = []
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
base, "run_on_transport",
|
base,
|
||||||
|
"run_on_transport",
|
||||||
lambda transport, line, stdin=None, timeout=60: sent.append((line, stdin)) or _Result(),
|
lambda transport, line, stdin=None, timeout=60: sent.append((line, stdin)) or _Result(),
|
||||||
)
|
)
|
||||||
args = {"sudo_password": sudo_password} if sudo_password else {}
|
args = {"sudo_password": sudo_password} if sudo_password else {}
|
||||||
|
|||||||
Reference in new issue
Block a user