Skip to content

Commit

Permalink
feat: prevent device/address change when linked to CMDB
Browse files Browse the repository at this point in the history
  • Loading branch information
kpetremann committed Oct 16, 2024
1 parent 17732ae commit 2d72a08
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 1 deletion.
5 changes: 5 additions & 0 deletions netbox_cmdb/netbox_cmdb/models/bgp.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
from utilities.choices import ChoiceSet
from utilities.querysets import RestrictedQuerySet

from netbox_cmdb import protect
from netbox_cmdb.choices import AssetMonitoringStateChoices, AssetStateChoices
from netbox_cmdb.constants import BGP_MAX_ASN, BGP_MIN_ASN


@protect.from_device_name_change("device")
class BGPGlobal(ChangeLoggedModel):
"""Global BGP configuration.
Expand Down Expand Up @@ -197,6 +199,7 @@ class Meta:
abstract = True


@protect.from_device_name_change("device")
class BGPPeerGroup(BGPSessionCommon):
"""A BGP Peer Group contains a set of BGP neighbors that shares common attributes."""

Expand Down Expand Up @@ -229,6 +232,8 @@ def get_absolute_url(self):
return reverse("plugins:netbox_cmdb:bgppeergroup", args=[self.pk])


@protect.from_device_name_change("device")
@protect.from_ip_address_change("local_address")
class DeviceBGPSession(BGPSessionCommon):
"""A Device BGP Session is a BGP session from a given device's perspective.
It contains BGP local parameters for the given devices (as the local address / ASN)."""
Expand Down
3 changes: 3 additions & 0 deletions netbox_cmdb/netbox_cmdb/models/bgp_community_list.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from django.db import models
from netbox.models import ChangeLoggedModel

from netbox_cmdb import protect


@protect.from_device_name_change("device")
class BGPCommunityList(ChangeLoggedModel):
"""An object used in RoutePolicy object to filter on a list of BGP communities."""

Expand Down
3 changes: 3 additions & 0 deletions netbox_cmdb/netbox_cmdb/models/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from django.db import models
from netbox.models import ChangeLoggedModel

from netbox_cmdb import protect
from netbox_cmdb.choices import AssetMonitoringStateChoices, AssetStateChoices

FEC_CHOICES = [
Expand All @@ -22,6 +23,7 @@
]


@protect.from_device_name_change("device")
class DeviceInterface(ChangeLoggedModel):
"""A device interface configuration."""

Expand Down Expand Up @@ -67,6 +69,7 @@ class Meta:
unique_together = ("device", "name")


@protect.from_ip_address_change("ipv4_address", "ipv6_address")
class LogicalInterface(ChangeLoggedModel):
"""A logical interface configuration."""

Expand Down
3 changes: 3 additions & 0 deletions netbox_cmdb/netbox_cmdb/models/prefix_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from netbox.models import ChangeLoggedModel
from utilities.choices import ChoiceSet

from netbox_cmdb import protect


class PrefixListIPVersionChoices(ChoiceSet):
"""Prefix list IP versions choices."""
Expand All @@ -19,6 +21,7 @@ class PrefixListIPVersionChoices(ChoiceSet):
)


@protect.from_device_name_change("device")
class PrefixList(ChangeLoggedModel):
"""Prefix list main model."""

Expand Down
2 changes: 2 additions & 0 deletions netbox_cmdb/netbox_cmdb/models/route_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@
from netbox.models import ChangeLoggedModel
from utilities.querysets import RestrictedQuerySet

from netbox_cmdb import protect
from netbox_cmdb.choices import DecisionChoice
from netbox_cmdb.fields import CustomIPAddressField


@protect.from_device_name_change("device")
class RoutePolicy(ChangeLoggedModel):
"""
A RoutePolicy contains a name and a description and is optionally linked to a Device.
Expand Down
2 changes: 2 additions & 0 deletions netbox_cmdb/netbox_cmdb/models/snmp.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django.db import models
from netbox.models import ChangeLoggedModel

from netbox_cmdb import protect
from netbox_cmdb.choices import SNMPCommunityType


Expand All @@ -23,6 +24,7 @@ class Meta:
verbose_name_plural = "SNMP Communities"


@protect.from_device_name_change("device")
class SNMP(ChangeLoggedModel):
"""A Snmp configuration"""

Expand Down
34 changes: 34 additions & 0 deletions netbox_cmdb/netbox_cmdb/protect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
MODELS_LINKED_TO_DEVICE = {}
MODELS_LINKED_TO_IP_ADDRESS = {}


def from_device_name_change(*fields):
def decorator(cls):
if cls not in MODELS_LINKED_TO_DEVICE:
MODELS_LINKED_TO_DEVICE[cls] = set()

if not fields:
return cls

for field in fields:
MODELS_LINKED_TO_DEVICE[cls].add(field)

return cls

return decorator


def from_ip_address_change(*fields):
def decorator(cls):
if cls not in MODELS_LINKED_TO_IP_ADDRESS:
MODELS_LINKED_TO_IP_ADDRESS[cls] = set()

if not fields:
return cls

for field in fields:
MODELS_LINKED_TO_IP_ADDRESS[cls].add(field)

return cls

return decorator
63 changes: 62 additions & 1 deletion netbox_cmdb/netbox_cmdb/signals.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
from django.db.models.signals import post_delete
from dcim.models import Device
from django.core.exceptions import ValidationError
from django.db.models.signals import post_delete, pre_save
from django.dispatch import receiver
from ipam.models import IPAddress

from netbox_cmdb import protect
from netbox_cmdb.models.bgp import BGPSession


Expand All @@ -13,3 +17,60 @@ def clean_device_bgp_sessions(sender, instance, **kwargs):
if instance.peer_b:
b = instance.peer_b
b.delete()


@receiver(pre_save, sender=Device)
def protect_from_device_name_change(sender, instance, **kwargs):
"""Prevents any name changes for dcim.Device if there is a CMDB object linked to it.
Some models in the CMDB depends on NetBox Device native model.
If one changes the Device name, it might affect the CMDB as a side effect, and could cause
unwanted configuration changes.
"""

if not instance.pk:
return

current = Device.objects.get(pk=instance.pk)

if current.name == instance.name:
return

for model, fields in protect.MODELS_LINKED_TO_DEVICE.items():
if not fields:
continue

for field in fields:
fitler = {field: instance}
if model.objects.filter(**fitler).exists():
raise ValidationError(
f"Device name cannot be changed because it is linked to: {model}."
)


@receiver(pre_save, sender=IPAddress)
def protect_from_ip_address_change(sender, instance, **kwargs):
"""Prevents any name changes for ipam.IPAddress if there is a CMDB object linked to it.
Some models in the CMDB depends on NetBox IPAddress native model.
If one changes the address, it might affect the CMDB as a side effect, and could cause
unwanted configuration changes.
"""
if not instance.pk:
return

current = IPAddress.objects.get(pk=instance.pk)

if current.address.ip == instance.address.ip:
return

for model, fields in protect.MODELS_LINKED_TO_IP_ADDRESS.items():
if not fields:
continue

for field in fields:
fitler = {field: instance}
if model.objects.filter(**fitler).exists():
raise ValidationError(
f"IP address cannot be changed because it is linked to: {model}."
)

0 comments on commit 2d72a08

Please sign in to comment.