Skip to content

Commit 2c757af

Browse files
authored
Merge pull request #742 from kr3ator/feature/device_type_components
Add support for DeviceType components
2 parents 27f2893 + 12753dd commit 2c757af

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

initializers/device_types.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,36 @@
2121
# slug: other
2222
# custom_field_data:
2323
# text_field: Description
24+
# interfaces:
25+
# - name: eth0
26+
# type: 1000base-t
27+
# mgmt_only: True
28+
# - name: eth1
29+
# type: 1000base-t
30+
# console_server_ports:
31+
# - name_template: ttyS[1-48]
32+
# type: rj-45
33+
# power_ports:
34+
# - name: psu0 # both non-template and template field specified; non-template field takes precedence
35+
# name_template: psu[0,1]
36+
# type: iec-60320-c14
37+
# maximum_draw: 35
38+
# allocated_draw: 35
39+
# front_ports:
40+
# - name_template: front[1,2]
41+
# type: 8p8c
42+
# rear_port_template: rear[0,1]
43+
# rear_port_position_template: "[1,2]"
44+
# rear_ports:
45+
# - name_template: rear[0,1]
46+
# type: 8p8c
47+
# positions_template: "[3,2]"
48+
# device_bays:
49+
# - name_template: bay[0-9]
50+
# label_template: test[0-5,9,6-8]
51+
# description: Test description
52+
# power_outlets:
53+
# - name_template: outlet[0,1]
54+
# type: iec-60320-c5
55+
# power_port: psu0
56+
# feed_leg: B

startup_scripts/190_device_types.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,71 @@
11
import sys
2+
from typing import List
23

34
from dcim.models import DeviceType, Manufacturer, Region
5+
from dcim.models.device_component_templates import (
6+
ConsolePortTemplate,
7+
ConsoleServerPortTemplate,
8+
DeviceBayTemplate,
9+
FrontPortTemplate,
10+
InterfaceTemplate,
11+
PowerOutletTemplate,
12+
PowerPortTemplate,
13+
RearPortTemplate,
14+
)
415
from startup_script_utils import (
516
load_yaml,
617
pop_custom_fields,
718
set_custom_fields_values,
819
split_params,
920
)
1021
from tenancy.models import Tenant
22+
from utilities.forms.utils import expand_alphanumeric_pattern
23+
24+
25+
def expand_templates(params: List[dict], device_type: DeviceType) -> List[dict]:
26+
templateable_fields = ["name", "label", "positions", "rear_port", "rear_port_position"]
27+
28+
expanded = []
29+
for param in params:
30+
param["device_type"] = device_type
31+
expanded_fields = {}
32+
has_plain_fields = False
33+
34+
for field in templateable_fields:
35+
template_value = param.pop(f"{field}_template", None)
36+
37+
if field in param:
38+
has_plain_fields = True
39+
expanded.append(param)
40+
elif template_value:
41+
expanded_fields[field] = list(expand_alphanumeric_pattern(template_value))
42+
43+
if expanded_fields and has_plain_fields:
44+
raise ValueError(f"Mix of plain and template keys provided for {templateable_fields}")
45+
46+
if not expanded_fields:
47+
continue
48+
49+
elements = list(expanded_fields.values())
50+
master_len = len(elements[0])
51+
if not all([len(elem) == master_len for elem in elements]):
52+
raise ValueError(
53+
f"Number of elements in template fields "
54+
f"{list(expanded_fields.keys())} must be equal"
55+
)
56+
57+
for idx in range(master_len):
58+
tmp = param.copy()
59+
for field, value in expanded_fields.items():
60+
if field in nested_assocs:
61+
model, match_key = nested_assocs[field]
62+
query = {match_key: value[idx], "device_type": device_type}
63+
tmp[field] = model.objects.get(**query)
64+
else:
65+
tmp[field] = value[idx]
66+
expanded.append(tmp)
67+
return expanded
68+
1169

1270
device_types = load_yaml("/opt/netbox/initializers/device_types.yml")
1371

@@ -17,9 +75,22 @@
1775
match_params = ["manufacturer", "model", "slug"]
1876
required_assocs = {"manufacturer": (Manufacturer, "name")}
1977
optional_assocs = {"region": (Region, "name"), "tenant": (Tenant, "name")}
78+
nested_assocs = {"rear_port": (RearPortTemplate, "name"), "power_port": (PowerPortTemplate, "name")}
79+
80+
supported_components = {
81+
"interfaces": (InterfaceTemplate, ["name"]),
82+
"console_ports": (ConsolePortTemplate, ["name"]),
83+
"console_server_ports": (ConsoleServerPortTemplate, ["name"]),
84+
"power_ports": (PowerPortTemplate, ["name"]),
85+
"power_outlets": (PowerOutletTemplate, ["name"]),
86+
"rear_ports": (RearPortTemplate, ["name"]),
87+
"front_ports": (FrontPortTemplate, ["name"]),
88+
"device_bays": (DeviceBayTemplate, ["name"]),
89+
}
2090

2191
for params in device_types:
2292
custom_field_data = pop_custom_fields(params)
93+
components = [(v[0], v[1], params.pop(k, [])) for k, v in supported_components.items()]
2394

2495
for assoc, details in required_assocs.items():
2596
model, field = details
@@ -41,3 +112,29 @@
41112
print("🔡 Created device type", device_type.manufacturer, device_type.model)
42113

43114
set_custom_fields_values(device_type, custom_field_data)
115+
116+
for component in components:
117+
c_model, c_match_params, c_params = component
118+
c_match_params.append("device_type")
119+
120+
if not c_params:
121+
continue
122+
123+
expanded_c_params = expand_templates(c_params, device_type)
124+
125+
for n_assoc, n_details in nested_assocs.items():
126+
n_model, n_field = n_details
127+
for c_param in expanded_c_params:
128+
if n_assoc in c_param:
129+
n_query = {n_field: c_param[n_assoc], "device_type": device_type}
130+
c_param[n_assoc] = n_model.objects.get(**n_query)
131+
132+
for new_param in expanded_c_params:
133+
new_matching_params, new_defaults = split_params(new_param, c_match_params)
134+
new_obj, new_obj_created = c_model.objects.get_or_create(
135+
**new_matching_params, defaults=new_defaults
136+
)
137+
if new_obj_created:
138+
print(
139+
f"🧷 Created {c_model._meta} {new_obj} component for device type {device_type}"
140+
)

0 commit comments

Comments
 (0)