Skip to content

Commit 34658b0

Browse files
authored
Merge pull request #107 from tobiasge/netbox-4.2
Prepare for Netbox 4.2
2 parents de301b9 + a241893 commit 34658b0

20 files changed

+204
-86
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Load data from YAML files into Netbox
77
First activate your virtual environment where Netbox is installed, the install the plugin version correspondig to your Netbox version.
88

99
```bash
10-
pip install "netbox-initializers==4.1.*"
10+
pip install "netbox-initializers==4.2.*"
1111
```
1212

1313
Then you need to add the plugin to the `PLUGINS` array in the Netbox configuration.
@@ -38,6 +38,6 @@ The initializers where a part of the Docker image and where then extracted into
3838
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:
3939

4040
```dockerfile
41-
FROM netboxcommunity/netbox:v4.1
42-
RUN /opt/netbox/venv/bin/pip install "netbox-initializers==4.1.*"
41+
FROM netboxcommunity/netbox:v4.2
42+
RUN /opt/netbox/venv/bin/pip install "netbox-initializers==4.2.*"
4343
```

pyproject.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ license = "Apache-2.0"
1414
dynamic = ["version"]
1515

1616
requires-python = ">=3.10"
17-
dependencies = ["ruamel-yaml>=0.18.6"]
17+
dependencies = ["ruamel-yaml>=0.18.10"]
1818

1919
[build-system]
2020
requires = ["hatchling"]
@@ -24,7 +24,7 @@ build-backend = "hatchling.build"
2424
path = "src/netbox_initializers/version.py"
2525

2626
[tool.uv]
27-
dev-dependencies = ["ruff==0.6.3"]
27+
dev-dependencies = ["ruff==0.9.1"]
2828

2929
[tool.ruff]
3030
line-length = 100

src/netbox_initializers/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ class NetBoxInitializersConfig(PluginConfig):
99
description = "Load initial data into Netbox"
1010
version = VERSION
1111
base_url = "initializers"
12-
min_version = "4.1-beta1"
13-
max_version = "4.1.99"
12+
min_version = "4.2.0"
13+
max_version = "4.2.99"
1414

1515

1616
config = NetBoxInitializersConfig

src/netbox_initializers/initializers/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from .interfaces import InterfaceInitializer
2323
from .ip_addresses import IPAddressInitializer
2424
from .locations import LocationInitializer
25+
from .macs import MACAddressInitializer
2526
from .manufacturers import ManufacturerInitializer
2627
from .object_permissions import ObjectPermissionInitializer
2728
from .platforms import PlatformInitializer

src/netbox_initializers/initializers/base.py

+18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Tuple
33

44
from core.models import ObjectType
5+
from dcim.models import MACAddress
56
from django.core.exceptions import ObjectDoesNotExist
67
from extras.models import CustomField, Tag
78
from ruamel.yaml import YAML
@@ -90,6 +91,22 @@ def set_tags(self, entity, tags):
9091
if save:
9192
entity.save()
9293

94+
def set_mac_addresses(self, entity, mac_addresses):
95+
if not mac_addresses:
96+
return
97+
98+
if not hasattr(entity, "mac_addresses"):
99+
raise Exception(f"⚠️ MAC Address cannot be applied to {entity}'s model")
100+
101+
save = False
102+
for mac_address in MACAddress.objects.filter(mac_address__in=mac_addresses):
103+
104+
entity.mac_addresses.add(mac_address)
105+
save = True
106+
107+
if save:
108+
entity.save()
109+
93110
def split_params(self, params: dict, unique_params: list = None) -> Tuple[dict, dict]:
94111
"""Split params dict into dict with matching params and a dict with default values"""
95112

@@ -140,6 +157,7 @@ class InitializationError(Exception):
140157
"prefix_vlan_roles",
141158
"vlan_groups",
142159
"vlans",
160+
"macs",
143161
"devices",
144162
"interfaces",
145163
"route_targets",

src/netbox_initializers/initializers/cables.py

+8-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Tuple
2-
3-
from circuits.models import Circuit, CircuitTermination, ProviderNetwork
2+
from circuits.constants import CIRCUIT_TERMINATION_TERMINATION_TYPES
3+
from circuits.models import Circuit, CircuitTermination
44
from dcim.models import (
55
Cable,
66
CableTermination,
@@ -13,12 +13,12 @@
1313
PowerPanel,
1414
PowerPort,
1515
RearPort,
16-
Site,
1716
)
1817
from django.contrib.contenttypes.models import ContentType
1918
from django.db.models import Q
2019

2120
from netbox_initializers.initializers.base import BaseInitializer, register_initializer
21+
from netbox_initializers.initializers.utils import get_scope_details
2222

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

58-
site_name = circuit_params.pop("site", None)
59-
provider_network = circuit_params.pop("provider_network", None)
60-
61-
if site_name:
62-
circuit_params["site"] = Site.objects.get(name=site_name)
63-
elif provider_network:
64-
circuit_params["provider_network"] = ProviderNetwork.objects.get(name=provider_network)
58+
if scope := circuit_params.pop("scope", None):
59+
scope_type, scope_id = get_scope_details(scope, CIRCUIT_TERMINATION_TERMINATION_TYPES)
60+
circuit_params["termination_type"] = scope_type
61+
circuit_params["termination_id"] = scope_id
6562
else:
6663
raise ValueError(
67-
f"⚠️ Missing one of required parameters: 'site' or 'provider_network' "
64+
f"⚠️ Missing required parameter: 'scope'"
6865
f"for side {term_side} of circuit {circuit}"
6966
)
7067

src/netbox_initializers/initializers/clusters.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
MATCH_PARAMS = ["name", "type"]
88
REQUIRED_ASSOCS = {"type": (ClusterType, "name")}
99
OPTIONAL_ASSOCS = {
10-
"site": (Site, "name"),
10+
"scope": (Site, "name"),
1111
"group": (ClusterGroup, "name"),
1212
"tenant": (Tenant, "name"),
1313
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from dcim.models import MACAddress
2+
3+
from netbox_initializers.initializers.base import BaseInitializer, register_initializer
4+
5+
6+
class MACAddressInitializer(BaseInitializer):
7+
data_file_name = "macs.yml"
8+
9+
def load_data(self):
10+
macs = self.load_yaml()
11+
if macs is None:
12+
return
13+
14+
for mac in macs:
15+
tags = mac.pop("tags", None)
16+
macaddress, created = MACAddress.objects.get_or_create(**mac)
17+
18+
if created:
19+
print("🗺️ Created MAC Address", macaddress.mac_address)
20+
21+
self.set_tags(macaddress, tags)
22+
23+
24+
register_initializer("macs", MACAddressInitializer)

src/netbox_initializers/initializers/prefixes.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
from dcim.models import Site
1+
from dcim.constants import LOCATION_SCOPE_TYPES
22
from ipam.models import VLAN, VRF, Prefix, Role
33
from netaddr import IPNetwork
44
from tenancy.models import Tenant, TenantGroup
55

66
from netbox_initializers.initializers.base import BaseInitializer, register_initializer
7+
from netbox_initializers.initializers.utils import get_scope_details
78

8-
MATCH_PARAMS = ["prefix", "site", "vrf", "vlan"]
9+
MATCH_PARAMS = ["prefix", "scope", "vrf", "vlan"]
910
OPTIONAL_ASSOCS = {
10-
"site": (Site, "name"),
1111
"tenant": (Tenant, "name"),
1212
"tenant_group": (TenantGroup, "name"),
1313
"vlan": (VLAN, "name"),
@@ -29,6 +29,9 @@ def load_data(self):
2929

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

32+
if scope := params.pop("scope"):
33+
params["scope_type"], params["scope_id"] = get_scope_details(scope, LOCATION_SCOPE_TYPES)
34+
3235
for assoc, details in OPTIONAL_ASSOCS.items():
3336
if assoc in params:
3437
model, field = details

src/netbox_initializers/initializers/users.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from django.utils.crypto import get_random_string
12
from users.models import Token, User
23

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

1415
for username, user_details in users.items():
1516
api_token = user_details.pop("api_token", Token.generate_key())
16-
password = user_details.pop("password", User.objects.make_random_password())
17+
password = user_details.pop("password", get_random_string(length=25))
1718
user, created = User.objects.get_or_create(username=username, defaults=user_details)
1819
if created:
1920
user.set_password(password)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from django.contrib.contenttypes.models import ContentType
2+
3+
4+
def get_scope_details(scope: dict, allowed_termination_types: list):
5+
try:
6+
scope_type = ContentType.objects.get(app_label__in=["dcim", "circuits"], model=scope["type"])
7+
if scope["type"] not in allowed_termination_types:
8+
raise ValueError(f"{scope['type']} scope type is not permitted on {scope_type.app_label}")
9+
except ContentType.DoesNotExist:
10+
raise ValueError(f"⚠️ Invalid scope type: {scope['type']}")
11+
12+
scope_id = scope_type.model_class().objects.get(name=scope["name"]).id
13+
return scope_type, scope_id

src/netbox_initializers/initializers/virtualization_interfaces.py

+11
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
from dcim.models import MACAddress
12
from virtualization.models import VirtualMachine, VMInterface
23

34
from netbox_initializers.initializers.base import BaseInitializer, register_initializer
45

56
MATCH_PARAMS = ["name", "virtual_machine"]
67
REQUIRED_ASSOCS = {"virtual_machine": (VirtualMachine, "name")}
8+
OPTIONAL_ASSOCS = {"primary_mac_address": (MACAddress, "mac_address")}
79

810

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

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

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

29+
for assoc, details in OPTIONAL_ASSOCS.items():
30+
if assoc in params:
31+
model, field = details
32+
query = {field: params.pop(assoc)}
33+
34+
params[assoc] = model.objects.get(**query)
35+
2636
matching_params, defaults = self.split_params(params, MATCH_PARAMS)
2737
interface, created = VMInterface.objects.get_or_create(
2838
**matching_params, defaults=defaults
@@ -33,6 +43,7 @@ def load_data(self):
3343

3444
self.set_custom_fields_values(interface, custom_field_data)
3545
self.set_tags(interface, tags)
46+
self.set_mac_addresses(interface, mac_addresses)
3647

3748

3849
register_initializer("virtualization_interfaces", VMInterfaceInitializer)

src/netbox_initializers/initializers/yaml/cables.yml

+6-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
# # termination_x_circuit:
1717
# # term_side -> termination side of a circuit. Must be A or B
1818
# # cid -> circuit ID value
19-
# # site OR provider_network -> name of Site or ProviderNetwork respectively. If both provided, Site takes precedence
19+
# # scope:
20+
# # type -> select one of the following: region, site, sitegroup, location
21+
# # name -> name of the object in the respective scope type
2022
# # ```
2123
# #
2224
# # If a termination is a power feed then the required parameter is termination_x_feed.
@@ -51,7 +53,9 @@
5153
# termination_b_circuit:
5254
# term_side: A
5355
# cid: Circuit_ID-1
54-
# site: AMS 1
56+
# scope:
57+
# type: site
58+
# name: AMS 1
5559
# type: cat6
5660

5761
# - termination_a_name: psu0

src/netbox_initializers/initializers/yaml/clusters.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
# tenant: tenant1
55
# - name: cluster2
66
# type: Hyper-V
7-
# site: SING 1
7+
# scope: SING 1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# - mac_address: 00:01:11:11:11:11
2+
# description: MAC address 1
3+
# - mac_address: 00:01:22:22:22:22
4+
# description: Mac address 2
5+
# - mac_address: 00:01:33:33:33:33
6+
# description: mac address 3
7+
# - mac_address: 00:02:44:44:44:44
8+
# description: mac address 4
9+
# - mac_address: 00:02:55:55:55:55
10+
# description: mac address 5

src/netbox_initializers/initializers/yaml/prefixes.yml

+23-3
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,46 @@
44
## - active
55
## - reserved
66
## - deprecated
7+
## scope:
8+
## type:
9+
## - region
10+
## - sitegroup
11+
## - site
12+
## - location
713
##
814
## Examples:
915

1016
# - description: prefix1
1117
# prefix: 10.1.1.0/24
12-
# site: AMS 1
18+
# scope:
19+
# type: site
20+
# name: AMS 1
1321
# status: active
1422
# tenant: tenant1
1523
# vlan: vlan1
1624
# - description: prefix2
1725
# prefix: 10.1.2.0/24
18-
# site: AMS 2
26+
# scope:
27+
# type: site
28+
# name: AMS 2
1929
# status: active
2030
# tenant: tenant2
2131
# vlan: vlan2
2232
# is_pool: true
2333
# vrf: vrf2
2434
# - description: ipv6 prefix1
2535
# prefix: 2001:db8:a000:1::/64
26-
# site: AMS 2
36+
# scope:
37+
# type: site
38+
# name: AMS 2
2739
# status: active
2840
# tenant: tenant2
2941
# vlan: vlan2
42+
# - description: ipv6 prefix2
43+
# prefix: 2001:db8:b000:1::/64
44+
# scope:
45+
# type: location
46+
# name: cage 101
47+
# status: active
48+
# tenant: tenant2
49+
# vlan: vlan1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
11
# - description: Network Interface 1
22
# enabled: true
3-
# mac_address: 00:77:77:77:77:77
3+
# primary_mac_address: 00:01:11:11:11:11
44
# mtu: 1500
55
# name: Network Interface 1
66
# virtual_machine: virtual machine 1
77
# - description: Network Interface 2
88
# enabled: true
9-
# mac_address: 00:55:55:55:55:55
9+
# mac_addresses:
10+
# - 00:01:22:22:22:22
11+
# - 00:01:33:33:33:33
12+
# primary_mac_address: 00:01:33:33:33:33
1013
# mtu: 1500
1114
# name: Network Interface 2
1215
# virtual_machine: virtual machine 1
16+
# - description: Network Interface 3
17+
# enabled: true
18+
# mtu: 1500
19+
# name: Network Interface 3
20+
# virtual_machine: virtual machine 2
21+
# - description: Network Interface 4
22+
# enabled: true
23+
# mac_addresses:
24+
# - 00:02:44:44:44:44
25+
# - 00:02:55:55:55:55
26+
# mtu: 1500
27+
# name: Network Interface 4
28+
# virtual_machine: virtual machine 2

src/netbox_initializers/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
VERSION = "4.1.0"
1+
VERSION = "4.2.0"

test/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM netboxcommunity/netbox:feature
1+
FROM netboxcommunity/netbox:v4.2
22

33
COPY ../ /opt/netbox-initializers/
44
COPY ./test/config/plugins.py /etc/netbox/config/

0 commit comments

Comments
 (0)