diff --git a/napalm_opnsense/opnsense.py b/napalm_opnsense/opnsense.py index 89dae3d..c8fdc2a 100644 --- a/napalm_opnsense/opnsense.py +++ b/napalm_opnsense/opnsense.py @@ -220,6 +220,11 @@ class OPNsenseDriver(OPNsensePingMixin, FirewallDriver): Each entry contains NAPALM standard keys: ``is_up``, ``is_enabled``, ``description``, ``last_flapped``, ``mac_address``, ``speed``, ``mtu``. + + Plus one non-standard key, ``identifier``: OPNsense's *assigned* + interface name ("lan", "opt1"), as opposed to the physical device the + dict is keyed by ("em0"). Empty for unassigned interfaces. This is the + name :meth:`send_wake_on_lan` requires — see the note there. """ data = self._get("/api/interfaces/overview/export") interfaces: dict[str, dict[str, Any]] = {} @@ -240,6 +245,7 @@ class OPNsenseDriver(OPNsensePingMixin, FirewallDriver): "mac_address": (iface.get("macaddr") or iface.get("mac") or "").lower(), "speed": float(iface["speed_mbps"]) if iface.get("speed_mbps") else 0.0, "mtu": int(iface["mtu"]) if iface.get("mtu") else 0, + "identifier": iface.get("identifier") or "", } return interfaces diff --git a/tests/unit/test_driver.py b/tests/unit/test_driver.py index 906b789..21b7970 100644 --- a/tests/unit/test_driver.py +++ b/tests/unit/test_driver.py @@ -61,6 +61,31 @@ INTERFACES_RESPONSE = { ] } +# Bare-list shape with the "identifier" field: OPNsense's assigned interface +# name, distinct from the physical "device" the dict is keyed by. Wake-on-LAN +# is addressed by the former, everything else in this driver by the latter. +IDENTIFIED_INTERFACES_RESPONSE = [ + { + "device": "em0", + "identifier": "lan", + "description": "LAN", + "status": "up", + "enabled": True, + "macaddr": "AA:BB:CC:DD:EE:FF", + "speed_mbps": 1000, + "mtu": 1500, + }, + { + "device": "em1", + "identifier": "opt1", + "description": "GUEST", + "status": "down", + "enabled": True, + "macaddr": "AA:BB:CC:DD:EE:00", + "mtu": 1500, + }, +] + # Real shape confirmed against a live OPNsense 24.7 instance's # GET /api/interfaces/overview/export (see issue #14): a bare top-level list # of interface dicts, addressed via "device" and CIDR "addr4"/"addr6" @@ -262,6 +287,20 @@ class TestGetInterfaces: ifaces = driver.get_interfaces() assert ifaces["em0"]["last_flapped"] == -1.0 + def test_identifier_passed_through(self, driver): + # OPNsense's *assigned* name, which is what send_wake_on_lan() needs — + # the dict key is the physical device ("em0"), which it rejects. + driver._get = lambda path: IDENTIFIED_INTERFACES_RESPONSE + ifaces = driver.get_interfaces() + assert ifaces["em0"]["identifier"] == "lan" + assert ifaces["em1"]["identifier"] == "opt1" + + def test_identifier_empty_when_unassigned(self, driver): + # An interface OPNsense has not assigned carries no identifier. + driver._get = lambda path: INTERFACES_RESPONSE + ifaces = driver.get_interfaces() + assert ifaces["em0"]["identifier"] == "" + def test_empty_response(self, driver): driver._get = lambda path: {"interfaces": []} assert driver.get_interfaces() == {}