Skip to content

Commit

Permalink
Merge pull request #107 from tobiasge/netbox-4.2
Browse files Browse the repository at this point in the history
Prepare for Netbox 4.2
  • Loading branch information
tobiasge authored Jan 27, 2025
2 parents de301b9 + a241893 commit 34658b0
Show file tree
Hide file tree
Showing 20 changed files with 204 additions and 86 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Load data from YAML files into Netbox
First activate your virtual environment where Netbox is installed, the install the plugin version correspondig to your Netbox version.

```bash
pip install "netbox-initializers==4.1.*"
pip install "netbox-initializers==4.2.*"
```

Then you need to add the plugin to the `PLUGINS` array in the Netbox configuration.
Expand Down Expand Up @@ -38,6 +38,6 @@ The initializers where a part of the Docker image and where then extracted into
To use the new plugin in a the Netbox Docker image, it musst be installad into the image. To this, the following example can be used as a starting point:

```dockerfile
FROM netboxcommunity/netbox:v4.1
RUN /opt/netbox/venv/bin/pip install "netbox-initializers==4.1.*"
FROM netboxcommunity/netbox:v4.2
RUN /opt/netbox/venv/bin/pip install "netbox-initializers==4.2.*"
```
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ license = "Apache-2.0"
dynamic = ["version"]

requires-python = ">=3.10"
dependencies = ["ruamel-yaml>=0.18.6"]
dependencies = ["ruamel-yaml>=0.18.10"]

[build-system]
requires = ["hatchling"]
Expand All @@ -24,7 +24,7 @@ build-backend = "hatchling.build"
path = "src/netbox_initializers/version.py"

[tool.uv]
dev-dependencies = ["ruff==0.6.3"]
dev-dependencies = ["ruff==0.9.1"]

[tool.ruff]
line-length = 100
Expand Down
4 changes: 2 additions & 2 deletions src/netbox_initializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ class NetBoxInitializersConfig(PluginConfig):
description = "Load initial data into Netbox"
version = VERSION
base_url = "initializers"
min_version = "4.1-beta1"
max_version = "4.1.99"
min_version = "4.2.0"
max_version = "4.2.99"


config = NetBoxInitializersConfig
1 change: 1 addition & 0 deletions src/netbox_initializers/initializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from .interfaces import InterfaceInitializer
from .ip_addresses import IPAddressInitializer
from .locations import LocationInitializer
from .macs import MACAddressInitializer
from .manufacturers import ManufacturerInitializer
from .object_permissions import ObjectPermissionInitializer
from .platforms import PlatformInitializer
Expand Down
18 changes: 18 additions & 0 deletions src/netbox_initializers/initializers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import Tuple

from core.models import ObjectType
from dcim.models import MACAddress
from django.core.exceptions import ObjectDoesNotExist
from extras.models import CustomField, Tag
from ruamel.yaml import YAML
Expand Down Expand Up @@ -90,6 +91,22 @@ def set_tags(self, entity, tags):
if save:
entity.save()

def set_mac_addresses(self, entity, mac_addresses):
if not mac_addresses:
return

if not hasattr(entity, "mac_addresses"):
raise Exception(f"⚠️ MAC Address cannot be applied to {entity}'s model")

save = False
for mac_address in MACAddress.objects.filter(mac_address__in=mac_addresses):

entity.mac_addresses.add(mac_address)
save = True

if save:
entity.save()

def split_params(self, params: dict, unique_params: list = None) -> Tuple[dict, dict]:
"""Split params dict into dict with matching params and a dict with default values"""

Expand Down Expand Up @@ -140,6 +157,7 @@ class InitializationError(Exception):
"prefix_vlan_roles",
"vlan_groups",
"vlans",
"macs",
"devices",
"interfaces",
"route_targets",
Expand Down
19 changes: 8 additions & 11 deletions src/netbox_initializers/initializers/cables.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Tuple

from circuits.models import Circuit, CircuitTermination, ProviderNetwork
from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
from circuits.models import Circuit, CircuitTermination
from dcim.models import (
Cable,
CableTermination,
Expand All @@ -13,12 +13,12 @@
PowerPanel,
PowerPort,
RearPort,
Site,
)
from django.contrib.contenttypes.models import ContentType
from django.db.models import Q

from netbox_initializers.initializers.base import BaseInitializer, register_initializer
from netbox_initializers.initializers.utils import get_scope_details

CONSOLE_PORT_TERMINATION = ContentType.objects.get_for_model(ConsolePort)
CONSOLE_SERVER_PORT_TERMINATION = ContentType.objects.get_for_model(ConsoleServerPort)
Expand Down Expand Up @@ -55,16 +55,13 @@ def get_termination_object(params: dict, side: str):
circuit = Circuit.objects.get(cid=circuit_params.pop("cid"))
term_side = circuit_params.pop("term_side").upper()

site_name = circuit_params.pop("site", None)
provider_network = circuit_params.pop("provider_network", None)

if site_name:
circuit_params["site"] = Site.objects.get(name=site_name)
elif provider_network:
circuit_params["provider_network"] = ProviderNetwork.objects.get(name=provider_network)
if scope := circuit_params.pop("scope", None):
scope_type, scope_id = get_scope_details(scope, CIRCUIT_TERMINATION_TERMINATION_TYPES)
circuit_params["termination_type"] = scope_type
circuit_params["termination_id"] = scope_id
else:
raise ValueError(
f"⚠️ Missing one of required parameters: 'site' or 'provider_network' "
f"⚠️ Missing required parameter: 'scope'"
f"for side {term_side} of circuit {circuit}"
)

Expand Down
2 changes: 1 addition & 1 deletion src/netbox_initializers/initializers/clusters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
MATCH_PARAMS = ["name", "type"]
REQUIRED_ASSOCS = {"type": (ClusterType, "name")}
OPTIONAL_ASSOCS = {
"site": (Site, "name"),
"scope": (Site, "name"),
"group": (ClusterGroup, "name"),
"tenant": (Tenant, "name"),
}
Expand Down
24 changes: 24 additions & 0 deletions src/netbox_initializers/initializers/macs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from dcim.models import MACAddress

from netbox_initializers.initializers.base import BaseInitializer, register_initializer


class MACAddressInitializer(BaseInitializer):
data_file_name = "macs.yml"

def load_data(self):
macs = self.load_yaml()
if macs is None:
return

for mac in macs:
tags = mac.pop("tags", None)
macaddress, created = MACAddress.objects.get_or_create(**mac)

if created:
print("🗺️ Created MAC Address", macaddress.mac_address)

self.set_tags(macaddress, tags)


register_initializer("macs", MACAddressInitializer)
9 changes: 6 additions & 3 deletions src/netbox_initializers/initializers/prefixes.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from dcim.models import Site
from dcim.constants import LOCATION_SCOPE_TYPES
from ipam.models import VLAN, VRF, Prefix, Role
from netaddr import IPNetwork
from tenancy.models import Tenant, TenantGroup

from netbox_initializers.initializers.base import BaseInitializer, register_initializer
from netbox_initializers.initializers.utils import get_scope_details

MATCH_PARAMS = ["prefix", "site", "vrf", "vlan"]
MATCH_PARAMS = ["prefix", "scope", "vrf", "vlan"]
OPTIONAL_ASSOCS = {
"site": (Site, "name"),
"tenant": (Tenant, "name"),
"tenant_group": (TenantGroup, "name"),
"vlan": (VLAN, "name"),
Expand All @@ -29,6 +29,9 @@ def load_data(self):

params["prefix"] = IPNetwork(params["prefix"])

if scope := params.pop("scope"):
params["scope_type"], params["scope_id"] = get_scope_details(scope, LOCATION_SCOPE_TYPES)

for assoc, details in OPTIONAL_ASSOCS.items():
if assoc in params:
model, field = details
Expand Down
3 changes: 2 additions & 1 deletion src/netbox_initializers/initializers/users.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from django.utils.crypto import get_random_string
from users.models import Token, User

from netbox_initializers.initializers.base import BaseInitializer, register_initializer
Expand All @@ -13,7 +14,7 @@ def load_data(self):

for username, user_details in users.items():
api_token = user_details.pop("api_token", Token.generate_key())
password = user_details.pop("password", User.objects.make_random_password())
password = user_details.pop("password", get_random_string(length=25))
user, created = User.objects.get_or_create(username=username, defaults=user_details)
if created:
user.set_password(password)
Expand Down
13 changes: 13 additions & 0 deletions src/netbox_initializers/initializers/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.contrib.contenttypes.models import ContentType


def get_scope_details(scope: dict, allowed_termination_types: list):
try:
scope_type = ContentType.objects.get(app_label__in=["dcim", "circuits"], model=scope["type"])
if scope["type"] not in allowed_termination_types:
raise ValueError(f"{scope['type']} scope type is not permitted on {scope_type.app_label}")
except ContentType.DoesNotExist:
raise ValueError(f"⚠️ Invalid scope type: {scope['type']}")

scope_id = scope_type.model_class().objects.get(name=scope["name"]).id
return scope_type, scope_id
11 changes: 11 additions & 0 deletions src/netbox_initializers/initializers/virtualization_interfaces.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from dcim.models import MACAddress
from virtualization.models import VirtualMachine, VMInterface

from netbox_initializers.initializers.base import BaseInitializer, register_initializer

MATCH_PARAMS = ["name", "virtual_machine"]
REQUIRED_ASSOCS = {"virtual_machine": (VirtualMachine, "name")}
OPTIONAL_ASSOCS = {"primary_mac_address": (MACAddress, "mac_address")}


class VMInterfaceInitializer(BaseInitializer):
Expand All @@ -16,13 +18,21 @@ def load_data(self):
for params in interfaces:
custom_field_data = self.pop_custom_fields(params)
tags = params.pop("tags", None)
mac_addresses = params.pop("mac_addresses", None)

for assoc, details in REQUIRED_ASSOCS.items():
model, field = details
query = {field: params.pop(assoc)}

params[assoc] = model.objects.get(**query)

for assoc, details in OPTIONAL_ASSOCS.items():
if assoc in params:
model, field = details
query = {field: params.pop(assoc)}

params[assoc] = model.objects.get(**query)

matching_params, defaults = self.split_params(params, MATCH_PARAMS)
interface, created = VMInterface.objects.get_or_create(
**matching_params, defaults=defaults
Expand All @@ -33,6 +43,7 @@ def load_data(self):

self.set_custom_fields_values(interface, custom_field_data)
self.set_tags(interface, tags)
self.set_mac_addresses(interface, mac_addresses)


register_initializer("virtualization_interfaces", VMInterfaceInitializer)
8 changes: 6 additions & 2 deletions src/netbox_initializers/initializers/yaml/cables.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
# # termination_x_circuit:
# # term_side -> termination side of a circuit. Must be A or B
# # cid -> circuit ID value
# # site OR provider_network -> name of Site or ProviderNetwork respectively. If both provided, Site takes precedence
# # scope:
# # type -> select one of the following: region, site, sitegroup, location
# # name -> name of the object in the respective scope type
# # ```
# #
# # If a termination is a power feed then the required parameter is termination_x_feed.
Expand Down Expand Up @@ -51,7 +53,9 @@
# termination_b_circuit:
# term_side: A
# cid: Circuit_ID-1
# site: AMS 1
# scope:
# type: site
# name: AMS 1
# type: cat6

# - termination_a_name: psu0
Expand Down
2 changes: 1 addition & 1 deletion src/netbox_initializers/initializers/yaml/clusters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
# tenant: tenant1
# - name: cluster2
# type: Hyper-V
# site: SING 1
# scope: SING 1
10 changes: 10 additions & 0 deletions src/netbox_initializers/initializers/yaml/macs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# - mac_address: 00:01:11:11:11:11
# description: MAC address 1
# - mac_address: 00:01:22:22:22:22
# description: Mac address 2
# - mac_address: 00:01:33:33:33:33
# description: mac address 3
# - mac_address: 00:02:44:44:44:44
# description: mac address 4
# - mac_address: 00:02:55:55:55:55
# description: mac address 5
26 changes: 23 additions & 3 deletions src/netbox_initializers/initializers/yaml/prefixes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,46 @@
## - active
## - reserved
## - deprecated
## scope:
## type:
## - region
## - sitegroup
## - site
## - location
##
## Examples:

# - description: prefix1
# prefix: 10.1.1.0/24
# site: AMS 1
# scope:
# type: site
# name: AMS 1
# status: active
# tenant: tenant1
# vlan: vlan1
# - description: prefix2
# prefix: 10.1.2.0/24
# site: AMS 2
# scope:
# type: site
# name: AMS 2
# status: active
# tenant: tenant2
# vlan: vlan2
# is_pool: true
# vrf: vrf2
# - description: ipv6 prefix1
# prefix: 2001:db8:a000:1::/64
# site: AMS 2
# scope:
# type: site
# name: AMS 2
# status: active
# tenant: tenant2
# vlan: vlan2
# - description: ipv6 prefix2
# prefix: 2001:db8:b000:1::/64
# scope:
# type: location
# name: cage 101
# status: active
# tenant: tenant2
# vlan: vlan1
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
# - description: Network Interface 1
# enabled: true
# mac_address: 00:77:77:77:77:77
# primary_mac_address: 00:01:11:11:11:11
# mtu: 1500
# name: Network Interface 1
# virtual_machine: virtual machine 1
# - description: Network Interface 2
# enabled: true
# mac_address: 00:55:55:55:55:55
# mac_addresses:
# - 00:01:22:22:22:22
# - 00:01:33:33:33:33
# primary_mac_address: 00:01:33:33:33:33
# mtu: 1500
# name: Network Interface 2
# virtual_machine: virtual machine 1
# - description: Network Interface 3
# enabled: true
# mtu: 1500
# name: Network Interface 3
# virtual_machine: virtual machine 2
# - description: Network Interface 4
# enabled: true
# mac_addresses:
# - 00:02:44:44:44:44
# - 00:02:55:55:55:55
# mtu: 1500
# name: Network Interface 4
# virtual_machine: virtual machine 2
2 changes: 1 addition & 1 deletion src/netbox_initializers/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
VERSION = "4.1.0"
VERSION = "4.2.0"
2 changes: 1 addition & 1 deletion test/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM netboxcommunity/netbox:feature
FROM netboxcommunity/netbox:v4.2

COPY ../ /opt/netbox-initializers/
COPY ./test/config/plugins.py /etc/netbox/config/
Expand Down
Loading

0 comments on commit 34658b0

Please sign in to comment.