diff --git a/napalm_device_types/__init__.py b/napalm_device_types/__init__.py index 8a9a5d6..596cc8c 100644 --- a/napalm_device_types/__init__.py +++ b/napalm_device_types/__init__.py @@ -90,6 +90,7 @@ from napalm_device_types.fixes import ( run_fix_steps, ) from napalm_device_types.health_metrics import HealthMetricsMixin +from napalm_device_types.firmware import FirmwareOfferMixin from napalm_device_types.licenses import LicenseMixin from napalm_device_types.host_reboot import HostRebootMixin from napalm_device_types.interface_filter import InterfaceFilterMixin @@ -116,6 +117,7 @@ from napalm_device_types.models import ( PowerAction, PowerStateDict, HypervisorHostDict, + FirmwareOfferDict, LicenseDict, FixResultDict, FixStepDict, @@ -171,6 +173,8 @@ from napalm_device_types.switch import SwitchDriver __all__ = [ "HypervisorHostDict", + "FirmwareOfferDict", + "FirmwareOfferMixin", "LicenseDict", "LicenseMixin", "EnsurePackageMixin", diff --git a/napalm_device_types/firmware.py b/napalm_device_types/firmware.py new file mode 100644 index 0000000..aed95e9 --- /dev/null +++ b/napalm_device_types/firmware.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +"""The firmware a vendor offers for a device. + +A fact for the caller (docs/STRUCTURE.md): a driver may know the newer firmware +from the vendor's download catalogue or the device's own update check; whether +running an older one is a problem is decided above the driver. Comparing the +vendor's version strings is vendor knowledge, so the driver says itself whether +the offer is newer. Declared only; a driver that can tell implements it. +""" + +from __future__ import annotations + +from typing import Optional, TYPE_CHECKING + +from napalm_device_types.models import FirmwareOfferDict + + +class FirmwareOfferMixin: + if TYPE_CHECKING: # pragma: no cover - declared for type checkers only + + def get_firmware_offer(self) -> Optional[FirmwareOfferDict]: + """The firmware running and the newest the vendor offers. + + None when the driver cannot tell for this device (an unknown model, + a catalogue that did not answer). + + Example:: + + {"current_version": "1.0.2.10", "latest_version": "1.0.2.12", + "newer": True, "download_url": "https://.../GS308E_1.0.2.12.zip"} + """ + ... diff --git a/napalm_device_types/models.py b/napalm_device_types/models.py index 0a4e9d9..cb9e8d9 100644 --- a/napalm_device_types/models.py +++ b/napalm_device_types/models.py @@ -1015,6 +1015,16 @@ class LicenseDict(TypedDict): withholds: NotRequired[List[str]] +class FirmwareOfferDict(TypedDict): + """The firmware a device runs and the newest its vendor offers + (``FirmwareOfferMixin.get_firmware_offer``). A fact, not a judgement.""" + + current_version: str + latest_version: str + newer: bool # the offer is newer than what runs, by the vendor's versioning + download_url: NotRequired[str] + + class HypervisorHostDict(TypedDict): """One host of a hypervisor or cluster (``HypervisorDriver.get_hypervisor_hosts``).""" diff --git a/pyproject.toml b/pyproject.toml index 9eab2a9..25af5dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "napalm-device-types" -version = "4.15.0" +version = "4.16.0" description = "Abstract device-type base classes for NAPALM drivers" readme = "README.md" requires-python = ">=3.10" diff --git a/tests/test_fact_getters.py b/tests/test_fact_getters.py index b6fa4e7..f715bfc 100644 --- a/tests/test_fact_getters.py +++ b/tests/test_fact_getters.py @@ -47,3 +47,18 @@ def test_the_getters_are_declared_only(): assert not hasattr(PackageProxyMixin, "get_package_proxy") assert not hasattr(LicenseMixin, "get_licenses") assert not hasattr(HypervisorDriver, "get_hypervisor_hosts") + + +def test_a_firmware_offer_says_what_runs_and_what_is_offered(): + """The vendor's newer firmware is a fact the driver can know (a download + catalogue, the device's own update check); whether it is a warning is the + application's call. Comparing vendor version strings stays with the driver, + so it says itself whether the offer is newer.""" + from napalm_device_types import FirmwareOfferDict, FirmwareOfferMixin + + hints = get_type_hints(FirmwareOfferDict) + assert set(hints) == {"current_version", "latest_version", "newer", "download_url"} + assert FirmwareOfferDict.__required_keys__ == frozenset( + {"current_version", "latest_version", "newer"} + ) + assert not hasattr(FirmwareOfferMixin, "get_firmware_offer")