feat: standards, shared CI and release tooling for NAPALM drivers
STANDARDS.md binds every repository in the NAPALM org: one default branch, Conventional Commits, the device-types contract, test-driven development, clean-code limits, a quality ratchet for existing code, packaging rules and releases by tag to PyPI. napalm-standard holds what a tool can check: the conformance check (with release rules on a tag), the quality ratchet (ported from netOrk's quality_ratchet.py), and the Gitea release from the CHANGELOG section. .gitea/workflows/driver.yml is the CI every driver calls; template/ is a new driver's starting point and passes the check as it is.
This commit is contained in:
commit
db6b2e4ea7
37 files changed
+2894
No files matched your search
@@ -0,0 +1,93 @@
|
||||
"""NAPALM driver for Example switches over SSH.
|
||||
|
||||
Optional args
|
||||
-------------
|
||||
port : int
|
||||
SSH port (default 22).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any, Protocol
|
||||
|
||||
from napalm.base.exceptions import ConnectionException
|
||||
from napalm.base.models import FactsDict
|
||||
from napalm_device_types import SwitchDriver
|
||||
|
||||
from napalm_example.parsers import parse_version
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
VENDOR = "Example"
|
||||
DEFAULT_PORT = 22
|
||||
|
||||
|
||||
class Transport(Protocol):
|
||||
def command(self, line: str) -> str: ...
|
||||
|
||||
def close(self) -> None: ...
|
||||
|
||||
|
||||
class ExampleDriver(SwitchDriver):
|
||||
"""A switch that answers line commands over SSH."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
hostname: str,
|
||||
username: str,
|
||||
password: str,
|
||||
timeout: int = 60,
|
||||
optional_args: dict[str, Any] | None = None,
|
||||
) -> None:
|
||||
self.hostname = hostname
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.timeout = timeout
|
||||
self.port = int((optional_args or {}).get("port", DEFAULT_PORT))
|
||||
self.transport: Transport | None = None
|
||||
|
||||
def open(self) -> None: # pragma: no cover - live I/O
|
||||
from netmiko import ConnectHandler
|
||||
|
||||
session = ConnectHandler(
|
||||
device_type="generic",
|
||||
host=self.hostname,
|
||||
port=self.port,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
timeout=self.timeout,
|
||||
)
|
||||
|
||||
class _Netmiko:
|
||||
def command(self, line: str) -> str:
|
||||
return str(session.send_command(line))
|
||||
|
||||
def close(self) -> None:
|
||||
session.disconnect()
|
||||
|
||||
self.transport = _Netmiko()
|
||||
|
||||
def close(self) -> None:
|
||||
if self.transport is not None:
|
||||
self.transport.close()
|
||||
self.transport = None
|
||||
|
||||
def _command(self, line: str) -> str:
|
||||
if self.transport is None:
|
||||
raise ConnectionException(f"{self.hostname}: not connected; call open() first")
|
||||
LOG.debug("%s: %s", self.hostname, line)
|
||||
return self.transport.command(line)
|
||||
|
||||
def get_facts(self) -> FactsDict:
|
||||
version = parse_version(self._command("show version"))
|
||||
return {
|
||||
"vendor": VENDOR,
|
||||
"model": version["model"],
|
||||
"hostname": version["hostname"],
|
||||
"fqdn": version["hostname"],
|
||||
"os_version": version["os_version"],
|
||||
"serial_number": version["serial_number"],
|
||||
"uptime": version["uptime"],
|
||||
"interface_list": [],
|
||||
}
|
||||
Reference in new issue
Block a user