Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea
.vscode
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
# netbox-interface-synchronization
## Overview
This plugin allows you to compare and synchronize interface names and types between devices and device types in NetBox. It can be useful for finding and correcting inconsistencies between interfaces when changing the device type.
MAJOR UPDATE

Thanks to a massive contribution from bastianleicht the Interface Synchronization plugin has become the Component Synchronization plugin.

This plugin allows you to compare and synchronize component names and types between devices and device types in NetBox 4+. It can be useful for finding and correcting inconsistencies between components when changing the device type.
## Compatibility
Tested with NetBox versions 4.0.0 - 4.3.1 This plugin is not compatible with Netbox 2 or 3
Tested with NetBox versions 4.3.1 This plugin is not compatible with Netbox 2 or 3

## Installation
If your NetBox 4 installation uses virtualenv, activate it like this:
```
Expand All @@ -27,7 +32,7 @@ Don't forget to restart NetBox:
sudo systemctl restart netbox
```
## Usage
To sync the interfaces, edit the device and set the new device type and save the device. Then find the "Sync Interfaces" button at the bottom of the page:
To sync the components, edit the device and set the new device type and save the device. Then find the "Sync Components" button at the bottom of the page:
![Device page](docs/images/1_device_page.png)
Mark the required actions with the checkboxes and click "Apply".
![Interface comparison](docs/images/2_interface_comparison.png)
Expand Down
11 changes: 8 additions & 3 deletions netbox_interface_synchronization/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,16 @@ class Config(PluginConfig):
name = 'netbox_interface_synchronization'
verbose_name = 'NetBox Interface Synchronization'
description = 'Syncing existing interface names and types with those from a new device type in NetBox'
version = '4.1.7'
author = 'Keith Knowles'
version = '5.0.0'
author = 'Keith Knowles and Bastian Leicht'
author_email = '[email protected]'
default_settings = {
'exclude_virtual_interfaces': True
'exclude_virtual_interfaces': True,
'include_interfaces_panel': False,
# Compare description during diff
# If compare is true, description will also be synced to device
# Otherwise not.
'compare_description': True
}


Expand Down
225 changes: 225 additions & 0 deletions netbox_interface_synchronization/comparison.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
from dataclasses import dataclass
from django.conf import settings

config = settings.PLUGINS_CONFIG["netbox_interface_synchronization"]


@dataclass(frozen=True)
class ParentComparison:
"""Common fields of a device component"""

id: int
name: str
label: str
description: str

def __eq__(self, other):
# Ignore some fields when comparing; ignore component name case and whitespaces
eq = (
self.name.lower().replace(" ", "") == other.name.lower().replace(" ", "")
) and (self.label == other.label)

if config["compare_description"]:
eq = eq and (self.description == other.description)

return eq

def __hash__(self):
# Ignore some fields when hashing; ignore component name case and whitespaces
return hash(self.name.lower().replace(" ", ""))

def __str__(self):
return f"Label: {self.label}\nDescription: {self.description}"


@dataclass(frozen=True)
class ParentTypedComparison(ParentComparison):
"""Common fields of a device typed component"""

type: str
type_display: str

def __eq__(self, other):
eq = (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.type == other.type)
)

if config["compare_description"]:
eq = eq and (self.description == other.description)

return eq

def __hash__(self):
return hash((self.name.lower().replace(" ", ""), self.type))

def __str__(self):
return f"{super().__str__()}\nType: {self.type_display}"


@dataclass(frozen=True)
class InterfaceComparison(ParentTypedComparison):
"""A unified way to represent the interface and interface template"""

mgmt_only: bool
is_template: bool = False

def __eq__(self, other):
eq = (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.type == other.type)
and (self.mgmt_only == other.mgmt_only)
)

if config["compare_description"]:
eq = eq and (self.description == other.description)

return eq

def __hash__(self):
return hash((self.name.lower().replace(" ", ""), self.type))

def __str__(self):
return f"{super().__str__()}\nManagement only: {self.mgmt_only}"


@dataclass(frozen=True)
class FrontPortComparison(ParentTypedComparison):
"""A unified way to represent the front port and front port template"""

color: str
# rear_port_id: int
rear_port_position: int
is_template: bool = False

def __eq__(self, other):
eq = (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.type == other.type)
and (self.color == other.color)
and (self.rear_port_position == other.rear_port_position)
)

if config["compare_description"]:
eq = eq and (self.description == other.description)

return eq

def __hash__(self):
return hash((self.name.lower().replace(" ", ""), self.type))

def __str__(self):
return f"{super().__str__()}\nColor: {self.color}\nPosition: {self.rear_port_position}"


@dataclass(frozen=True)
class RearPortComparison(ParentTypedComparison):
"""A unified way to represent the rear port and rear port template"""

color: str
positions: int
is_template: bool = False

def __eq__(self, other):
eq = (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.type == other.type)
and (self.color == other.color)
and (self.positions == other.positions)
)

if config["compare_description"]:
eq = eq and (self.description == other.description)

return eq

def __hash__(self):
return hash((self.name.lower().replace(" ", ""), self.type))

def __str__(self):
return f"{super().__str__()}\nColor: {self.color}\nPositions: {self.positions}"


@dataclass(frozen=True, eq=False)
class ConsolePortComparison(ParentTypedComparison):
"""A unified way to represent the consoleport and consoleport template"""

is_template: bool = False


@dataclass(frozen=True, eq=False)
class ConsoleServerPortComparison(ParentTypedComparison):
"""A unified way to represent the consoleserverport and consoleserverport template"""

is_template: bool = False


@dataclass(frozen=True)
class PowerPortComparison(ParentTypedComparison):
"""A unified way to represent the power port and power port template"""

maximum_draw: str
allocated_draw: str
is_template: bool = False

def __eq__(self, other):
eq = (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.type == other.type)
and (self.maximum_draw == other.maximum_draw)
and (self.allocated_draw == other.allocated_draw)
)

if config["compare_description"]:
eq = eq and (self.description == other.description)

return eq

def __hash__(self):
return hash((self.name.lower().replace(" ", ""), self.type))

def __str__(self):
return f"{super().__str__()}\nMaximum draw: {self.maximum_draw}\nAllocated draw: {self.allocated_draw}"


@dataclass(frozen=True)
class PowerOutletComparison(ParentTypedComparison):
"""A unified way to represent the power outlet and power outlet template"""

power_port_name: str = ""
feed_leg: str = ""
is_template: bool = False

def __eq__(self, other):
eq = (
(self.name.lower().replace(" ", "") == other.name.lower().replace(" ", ""))
and (self.label == other.label)
and (self.type == other.type)
and (self.power_port_name == other.power_port_name)
and (self.feed_leg == other.feed_leg)
)

if config["compare_description"]:
eq = eq and (self.description == other.description)

return eq

def __hash__(self):
return hash(
(self.name.lower().replace(" ", ""), self.type, self.power_port_name)
)

def __str__(self):
return f"{super().__str__()}\nPower port name: {self.power_port_name}\nFeed leg: {self.feed_leg}"


@dataclass(frozen=True, eq=False)
class DeviceBayComparison(ParentComparison):
"""A unified way to represent the device bay and device bay template"""

is_template: bool = False
2 changes: 1 addition & 1 deletion netbox_interface_synchronization/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from django import forms


class InterfaceComparisonForm(forms.Form):
class ComponentComparisonForm(forms.Form):
add_to_device = forms.BooleanField(required=False)
remove_from_device = forms.BooleanField(required=False)
2 changes: 1 addition & 1 deletion netbox_interface_synchronization/template_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class DeviceViewExtension(PluginTemplateExtension):
def buttons(self):
"""Implements a compare interfaces button at the top of the page"""
obj = self.context['object']
return self.render("netbox_interface_synchronization/compare_interfaces_button.html", extra_context={
return self.render("netbox_interface_synchronization/compare_components_button.html", extra_context={
"device": obj
})

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
{% if perms.dcim.change_device %}
<div class="dropdown">
<button id="compare-device-components" type="button" class="btn btn-purple dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
Device type sync
</button>
<ul class="dropdown-menu" aria-labeled-by="add-device-components">
{% if perms.dcim.add_consoleport %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_synchronization:consoleport_comparison' device_id=device.id %}">
Console Ports
</a>
</li>
{% endif %}
{% if perms.dcim.add_consoleserverport %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_synchronization:consoleserverport_comparison' device_id=device.id %}">
Console Server Ports
</a>
</li>
{% endif %}
{% if perms.dcim.add_powerport %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_synchronization:powerport_comparison' device_id=device.id %}">
Power Ports
</a>
</li>
{% endif %}
{% if perms.dcim.add_poweroutlet %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_synchronization:poweroutlet_comparison' device_id=device.id %}">
Power Outlets
</a>
</li>
{% endif %}
{% if perms.dcim.add_interface %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_synchronization:interface_comparison' device_id=device.id %}">
Interfaces
</a>
</li>
{% endif %}
{% if perms.dcim.add_frontport %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_synchronization:frontport_comparison' device_id=device.id %}">
Front Ports
</a>
</li>
{% endif %}
{% if perms.dcim.add_rearport %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_synchronization:rearport_comparison' device_id=device.id %}">
Rear Ports
</a>
</li>
{% endif %}
{% if perms.dcim.add_devicebay %}
<li>
<a class="dropdown-item" href="{% url 'plugins:netbox_interface_synchronization:devicebay_comparison' device_id=device.id %}">
Device Bays
</a>
</li>
{% endif %}
</ul>
</div>
{% endif %}

This file was deleted.

Loading