Skip to content

Commit 3ab6d4e

Browse files
authored
Merge pull request #373 from nautobot/release-4.2.5
Release 4.2.5
2 parents ae5d685 + 926ec48 commit 3ab6d4e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+5034
-367
lines changed

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ jobs:
9292
fail-fast: true
9393
matrix:
9494
python-version: ["3.11"]
95-
nautobot-version: ["2.2.3"]
95+
nautobot-version: ["2.3.1"]
9696
env:
9797
INVOKE_NAUTOBOT_DEVICE_ONBOARDING_PYTHON_VER: "${{ matrix.python-version }}"
9898
INVOKE_NAUTOBOT_DEVICE_ONBOARDING_NAUTOBOT_VER: "${{ matrix.nautobot-version }}"
@@ -145,7 +145,7 @@ jobs:
145145
include:
146146
- python-version: "3.11"
147147
db-backend: "postgresql"
148-
nautobot-version: "2.2.3"
148+
nautobot-version: "2.3.1"
149149
- python-version: "3.12"
150150
db-backend: "mysql"
151151
nautobot-version: "stable"

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,17 @@ Regardless, the Onboarding App greatly simplifies the onboarding process by allo
1919

2020
### Support Matrix (Sync Devices From Network)
2121

22+
2223
| Data Attribute | Cisco IOS | Cisco XE | Cisco NXOS | Cisco WLC | Juniper Junos | Arista EOS | F5 | HP Comware | Palo Alto Panos | Aruba AOSCX |
2324
| ----------------------- | :----------------: | :--------------: | :--------------: | :--------------: | :--------------: | :--------------: | :-: | :-: | :-: | :-: |
24-
| Hostname |||||||| 🧪 | 🧪 | 🧪 |
25-
| Platform |||||||| 🧪 | 🧪 | 🧪 |
26-
| Manufacturer |||||||| 🧪 | 🧪 | 🧪 |
27-
| Serial Number |||||||| 🧪 | 🧪 | 🧪 |
28-
| Device Type |||||||| 🧪 | 🧪 | 🧪 |
29-
| Mgmt Interface |||||||| 🧪 | 🧪 | 🧪 |
30-
| Mgmt IP Address |||||||| 🧪 | 🧪 | 🧪 |
25+
| Hostname ||||||| 🧪 | 🧪 | 🧪 | 🧪 |
26+
| Platform ||||||| 🧪 | 🧪 | 🧪 | 🧪 |
27+
| Manufacturer ||||||| 🧪 | 🧪 | 🧪 | 🧪 |
28+
| Serial Number ||||||| 🧪 | 🧪 | 🧪 | 🧪 |
29+
| Device Type ||||||| 🧪 | 🧪 | 🧪 | 🧪 |
30+
| Mgmt Interface ||||||| 🧪 | 🧪 | 🧪 | 🧪 |
31+
| Mgmt IP Address ||||||| 🧪 | 🧪 | 🧪 | 🧪 |
32+
3133

3234
### Support Matrix (Sync Data From Network)
3335

@@ -43,6 +45,7 @@ Regardless, the Onboarding App greatly simplifies the onboarding process by allo
4345
| 802.1Q mode ||||||||
4446
| Lag Member ||||||||
4547
| Vrf Membership ||||||||
48+
| Software Version ||||||||
4649

4750
| VLANS | Cisco IOS | Cisco XE | Cisco NXOS | Cisco WLC | Juniper Junos | Arista EOS | F5 |
4851
| ----------------------- | :----------------: | :--------------: | :--------------: | :--------------: | :--------------: | :--------------: | :-: |

docs/admin/install.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Here you will find detailed instructions on how to **install** and **configure**
44

55
## Prerequisites
66

7-
- The app is compatible with Nautobot 2.0.3 and higher.
7+
- The app is compatible with Nautobot 2.3.1 and higher.
88
- Databases supported: PostgreSQL, MySQL
99

1010
!!! note

docs/admin/release_notes/version_4.2.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,31 @@ Versioning](https://semver.org/spec/v2.0.0.html).
1313
- [#340](https://github.com/nautobot/nautobot-app-device-onboarding/issues/340) - Add Aruba AOSCX support for Sync Devices from Network Job.
1414
- [#278](https://github.com/nautobot/nautobot-app-device-onboarding/issues/278) - Optimized VLAN loading into diffsync from Nautbot
1515
- [#278](https://github.com/nautobot/nautobot-app-device-onboarding/issues/278) - Improved error handling when creating VLANs
16+
- [#233](https://github.com/nautobot/nautobot-app-device-onboarding/issues/233) - Added support syncing in software versions from devices to nautobot core models.
17+
- [#334](https://github.com/nautobot/nautobot-app-device-onboarding/issues/334) - Add initial F5 Support for Network Device Sync
18+
- [#357](https://github.com/nautobot/nautobot-app-device-onboarding/issues/357) - Added improved error messaging for device object `get()` call in the `update()` method of `SyncDevicesDevice`
19+
- [#372](https://github.com/nautobot/nautobot-app-device-onboarding/issues/372) - Fixed bug when loading Nautobot Vlans with multiple locations assigned. Only Vlans with 1 location will be considered for the sync.
20+
21+
## [v4.2.5 (2025-05-13)](https://github.com/nautobot/nautobot-app-device-onboarding/releases/tag/v4.2.5)
22+
23+
### Added
24+
25+
- [#233](https://github.com/nautobot/nautobot-app-device-onboarding/issues/233) - Added support syncing in software versions from devices to nautobot core models.
26+
- [#334](https://github.com/nautobot/nautobot-app-device-onboarding/issues/334) - Add initial F5 Support for Network Device Sync
27+
- [#357](https://github.com/nautobot/nautobot-app-device-onboarding/issues/357) - Added improved error messaging for device object `get()` call in the `update()` method of `SyncDevicesDevice`
28+
29+
### Fixed
30+
31+
- [#372](https://github.com/nautobot/nautobot-app-device-onboarding/issues/372) - Fixed bug when loading Nautobot Vlans with multiple locations assigned. Only Vlans with 1 location will be considered for the sync.
32+
33+
### Dependencies
34+
35+
- [#367](https://github.com/nautobot/nautobot-app-device-onboarding/issues/367) - Updated jdiff dependency pin.
36+
37+
### Documentation
38+
39+
- [#358](https://github.com/nautobot/nautobot-app-device-onboarding/issues/358) - Updated documentation on the README for VRF.
40+
1641

1742
## [v4.2.4 (2025-04-08)](https://github.com/nautobot/nautobot-app-device-onboarding/releases/tag/v4.2.4)
1843

docs/user/app_overview.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Expose two new SSoT based Nautobot jobs to perform the syncing of data.
4747
- VRFs
4848
- VRF Names
4949
- Route Distinguishers (RD)
50+
- Cabling
51+
- Software Version
5052
- Cabling (**Note** Cables attached to Circuits will be skipped)
5153

5254
!!! info

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ markdown_extensions:
7979
permalink: true
8080
- "attr_list"
8181
- "md_in_html"
82+
- "pymdownx.details"
8283
- "pymdownx.highlight":
8384
anchor_linenums: true
8485
- "pymdownx.inlinehilite"

nautobot_device_onboarding/command_mappers/arista_eos.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,8 @@ sync_network_data:
116116
jpath: '{admin_mode: switchports."{{ current_key }}".switchportInfo.mode, mode: switchports."{{ current_key }}".switchportInfo.mode, access_vlan: switchports."{{ current_key }}".switchportInfo.accessVlanId, trunking_vlans: switchports."{{ current_key }}".switchportInfo.trunkAllowedVlans, native_vlan: switchports."{{ current_key }}".switchportInfo.trunkingNativeVlanId}' # yamllint disable-line rule:quoted-strings
117117
post_processor: "{{ obj | get_vlan_data(vlan_map, 'untagged') | tojson }}"
118118
iterable_type: "dict"
119+
software_version:
120+
commands:
121+
- command: "show version"
122+
parser: "textfsm"
123+
jpath: "[*].image"

nautobot_device_onboarding/command_mappers/cisco_ios.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,8 @@ sync_network_data:
129129
parser: "textfsm"
130130
jpath: "[*].{local_interface:local_interface, remote_interface:neighbor_interface, remote_device:neighbor_name}"
131131
post_processor: "{% set result = [] %}{% for cable in obj %}{% set _=result.append({'local_interface': cable['local_interface'], 'remote_interface': cable['remote_interface'], 'remote_device': cable['remote_device'] | remove_fqdn }) %}{% endfor %}{{ result | tojson }}"
132+
software_version:
133+
commands:
134+
- command: "show version"
135+
parser: "textfsm"
136+
jpath: "[*].version"

nautobot_device_onboarding/command_mappers/cisco_nxos.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,8 @@ sync_network_data:
122122
parser: "textfsm"
123123
jpath: "[*].{local_interface:local_interface, remote_interface:neighbor_interface, remote_device:neighbor_name}"
124124
post_processor: "{% set result = [] %}{% for cable in obj %}{% set _=result.append({'local_interface': cable['local_interface'], 'remote_interface': cable['remote_interface'], 'remote_device': cable['remote_device'] | remove_fqdn }) %}{% endfor %}{{ result | tojson }}"
125+
software_version:
126+
commands:
127+
- command: "show version"
128+
parser: "textfsm"
129+
jpath: "[*].os"

nautobot_device_onboarding/command_mappers/cisco_xe.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,8 @@ sync_network_data:
126126
parser: "textfsm"
127127
jpath: "[*].{local_interface:local_interface, remote_interface:neighbor_interface, remote_device:neighbor_name}"
128128
post_processor: "{% set result = [] %}{% for cable in obj %}{% set _=result.append({'local_interface': cable['local_interface'], 'remote_interface': cable['remote_interface'], 'remote_device': cable['remote_device'] | remove_fqdn }) %}{% endfor %}{{ result | tojson }}"
129+
software_version:
130+
commands:
131+
- command: "show version"
132+
parser: "textfsm"
133+
jpath: "[*].version"
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
sync_devices:
3+
hostname:
4+
commands:
5+
- command: "show cm device"
6+
parser: "ttp"
7+
jpath: "[]|[?mgmt_ip==`{{ obj }}`].mgmt_hostname"
8+
post_processor: "{{ obj[0] }}"
9+
interable_type: "str"
10+
serial:
11+
commands:
12+
- command: "show sys hardware"
13+
parser: "ttp"
14+
jpath: "[*].serial_number"
15+
post_processor: "{{ obj[0] }}"
16+
interable_type: "str"
17+
device_type:
18+
commands:
19+
- command: "show sys hardware"
20+
parser: "ttp"
21+
jpath: "[*].model_type"
22+
post_processor: "{{ obj[0] }}"
23+
interable_type: "str"
24+
mgmt_interface:
25+
commands:
26+
- command: "show net interface | grep mgmt | grep up"
27+
parser: "raw"
28+
jpath: "raw"
29+
post_processor: "{{ obj.split(' ')[0] }}"
30+
mask_length:
31+
commands:
32+
- command: "show sys cluster"
33+
parser: "ttp"
34+
jpath: "[*].mgmt_mask"
35+
post_processor: "{{ obj[0] }}"
36+
iterable_type: "int"

nautobot_device_onboarding/command_mappers/juniper_junos.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,3 +121,8 @@ sync_network_data:
121121
jpath: '"lldp-neighbors-information"[]."lldp-neighbor-information"[].{local_interface: "lldp-local-port-id"[0].data, remote_interface: "lldp-remote-port-id"[0].data, remote_device: "lldp-remote-system-name"[0].data}' # yamllint disable-line rule:quoted-strings
122122
post_processor: "{% set result = [] %}{% for cable in obj %}{% set _=result.append({'local_interface': cable['local_interface'], 'remote_interface': cable['remote_interface'], 'remote_device': cable['remote_device'] | remove_fqdn }) %}{% endfor %}{{ result | tojson }}"
123123
iterable_type: "dict"
124+
software_version:
125+
commands:
126+
- command: "show system information | display json"
127+
parser: "none"
128+
jpath: '"system-information"[]."os-version"[].data' # yamllint disable-line rule:quoted-strings

nautobot_device_onboarding/diffsync/adapters/sync_network_data_adapters.py

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from django.conf import settings
88
from django.contrib.contenttypes.models import ContentType
99
from django.core.exceptions import ValidationError
10-
from nautobot.dcim.models import Interface
10+
from nautobot.dcim.models import Device, Interface, SoftwareVersion
1111
from nautobot.ipam.models import VLAN, VRF, IPAddress
1212
from nautobot_ssot.contrib import NautobotAdapter
1313
from netaddr import EUI, mac_unix_expanded
@@ -52,6 +52,8 @@ class SyncNetworkDataNautobotAdapter(FilteredNautobotAdapter):
5252
lag_to_interface = sync_network_data_models.SyncNetworkDataLagToInterface
5353
vrf_to_interface = sync_network_data_models.SyncNetworkDataVrfToInterface
5454
cable = sync_network_data_models.SyncNetworkDataCable
55+
software_version = sync_network_data_models.SyncNetworkSoftwareVersion
56+
software_version_to_device = sync_network_data_models.SyncNetworkSoftwareVersionToDevice
5557

5658
primary_ips = None
5759

@@ -66,6 +68,8 @@ class SyncNetworkDataNautobotAdapter(FilteredNautobotAdapter):
6668
"lag_to_interface",
6769
"vrf_to_interface",
6870
"cable",
71+
"software_version",
72+
"software_version_to_device",
6973
]
7074

7175
def _cache_primary_ips(self, device_queryset):
@@ -134,7 +138,15 @@ def load_vlans(self):
134138
"""
135139
# TODO: update this to support multiple locations per VLAN after the setting for this feature has been added.
136140
location_ids = list(self.job.devices_to_load.values_list("location__id", flat=True))
137-
for vlan in VLAN.objects.filter(location__in=location_ids):
141+
for vlan in VLAN.objects.filter(locations__in=location_ids):
142+
if (
143+
vlan.locations.count() > 1
144+
): # TODO: A conditional check will be needed here to support multiple locations per VLAN
145+
if self.job.debug:
146+
self.job.logger.debug(
147+
f"Vlan {vlan.name} has multiple locations. Skipping Vlan load for {vlan.name}."
148+
)
149+
continue
138150
network_vlan = self.vlan(
139151
adapter=self,
140152
name=vlan.name,
@@ -305,6 +317,32 @@ def load_cables(self):
305317
except diffsync.exceptions.ObjectAlreadyExists:
306318
continue
307319

320+
def load_software_versions(self):
321+
"""Load Software Versions into the Diffsync store."""
322+
for software_version in SoftwareVersion.objects.all():
323+
network_software_version = self.software_version(
324+
adapter=self,
325+
version=software_version.version,
326+
platform__name=software_version.platform.name,
327+
)
328+
try:
329+
network_software_version.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
330+
self.add(network_software_version)
331+
except diffsync.exceptions.ObjectAlreadyExists:
332+
continue
333+
334+
def load_software_version_to_device(self):
335+
"""Load Software Version to Device assignments into the Diffsync store."""
336+
for device in self.job.devices_to_load:
337+
network_software_version_to_device = self.software_version_to_device(
338+
adapter=self,
339+
name=device.name,
340+
serial=device.serial,
341+
software_version__version=device.software_version.version if device.software_version else "",
342+
)
343+
network_software_version_to_device.model_flags = DiffSyncModelFlags.SKIP_UNMATCHED_DST
344+
self.add(network_software_version_to_device)
345+
308346
def load(self):
309347
"""Generic implementation of the load function."""
310348
if not hasattr(self, "top_level") or not self.top_level:
@@ -334,6 +372,12 @@ def load(self):
334372
elif model_name == "cable":
335373
if self.job.sync_cables:
336374
self.load_cables()
375+
elif model_name == "software_version":
376+
if self.job.sync_software_version:
377+
self.load_software_versions()
378+
elif model_name == "software_version_to_device":
379+
if self.job.sync_software_version:
380+
self.load_software_version_to_device()
337381
else:
338382
diffsync_model = self._get_diffsync_class(model_name)
339383
self._load_objects(diffsync_model)
@@ -409,6 +453,8 @@ def __init__(self, *args, job, sync=None, **kwargs):
409453
lag_to_interface = sync_network_data_models.SyncNetworkDataLagToInterface
410454
vrf_to_interface = sync_network_data_models.SyncNetworkDataVrfToInterface
411455
cable = sync_network_data_models.SyncNetworkDataCable
456+
software_version = sync_network_data_models.SyncNetworkSoftwareVersion
457+
software_version_to_device = sync_network_data_models.SyncNetworkSoftwareVersionToDevice
412458

413459
top_level = [
414460
"ip_address",
@@ -421,6 +467,8 @@ def __init__(self, *args, job, sync=None, **kwargs):
421467
"lag_to_interface",
422468
"vrf_to_interface",
423469
"cable",
470+
"software_version",
471+
"software_version_to_device",
424472
]
425473

426474
def _handle_failed_devices(self, device_data):
@@ -890,6 +938,46 @@ def load_cables(self): # pylint: disable=inconsistent-return-statements
890938
model_type="cable",
891939
)
892940

941+
def load_software_versions(self):
942+
"""Load software versions into the Diffsync store."""
943+
for ( # pylint: disable=too-many-nested-blocks
944+
hostname,
945+
device_data,
946+
) in self.job.command_getter_result.items():
947+
if self.job.debug:
948+
self.job.logger.debug(f"Loading Software Versions from {hostname}")
949+
if device_data["software_version"]:
950+
device = Device.objects.get(serial=device_data["serial"])
951+
try:
952+
network_software_version = self.software_version(
953+
adapter=self,
954+
platform__name=device.platform.name,
955+
version=device_data["software_version"],
956+
)
957+
self.add(network_software_version)
958+
except diffsync.exceptions.ObjectAlreadyExists:
959+
continue
960+
961+
def load_software_version_to_device(self):
962+
"""Load software version to device assignments into the Diffsync store."""
963+
for ( # pylint: disable=too-many-nested-blocks
964+
hostname,
965+
device_data,
966+
) in self.job.command_getter_result.items():
967+
if self.job.debug:
968+
self.job.logger.debug(f"Loading Software Version to Device assignments from {hostname}")
969+
if device_data["software_version"]:
970+
try:
971+
network_software_version_to_device = self.software_version_to_device(
972+
adapter=self,
973+
name=hostname,
974+
serial=device_data["serial"],
975+
software_version__version=device_data["software_version"],
976+
)
977+
self.add(network_software_version_to_device)
978+
except diffsync.exceptions.ObjectAlreadyExists:
979+
continue
980+
893981
def load(self):
894982
"""Load network data."""
895983
self.execute_command_getter()
@@ -908,3 +996,7 @@ def load(self):
908996
self.load_vrf_to_interface()
909997
if self.job.sync_cables:
910998
self.load_cables()
999+
if self.job.sync_software_version:
1000+
self.load_software_versions()
1001+
if self.job.sync_software_version:
1002+
self.load_software_version_to_device()

nautobot_device_onboarding/diffsync/models/sync_devices_models.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,14 @@ def create(cls, adapter, ids, attrs):
249249

250250
def update(self, attrs):
251251
"""Update an existing nautobot device using data scraped from a device."""
252-
device = Device.objects.get(name=self.name, location__name=self.location__name)
252+
try:
253+
device = Device.objects.get(name=self.name, location__name=self.location__name)
254+
except MultipleObjectsReturned as exc:
255+
raise MultipleObjectsReturned(
256+
f"Multiple devices found with name {self.name} and location {self.location__name}"
257+
) from exc
258+
except ObjectDoesNotExist as exc:
259+
raise ObjectDoesNotExist(f"Device {self.name} does not exist at {self.location__name}") from exc
253260

254261
if self.adapter.job.debug:
255262
self.adapter.job.logger.debug(f"Updating {device.name} with attrs: {attrs}")

0 commit comments

Comments
 (0)