diff --git a/src/netbox_initializers/__init__.py b/src/netbox_initializers/__init__.py index 2099aa0..ea89f52 100644 --- a/src/netbox_initializers/__init__.py +++ b/src/netbox_initializers/__init__.py @@ -8,7 +8,7 @@ class NetBoxInitializersConfig(PluginConfig): version = "4.0.0" base_url = "initializers" min_version = "4.0-beta1" - max_version = "4.0.99" + max_version = "4.1.99" config = NetBoxInitializersConfig diff --git a/src/netbox_initializers/initializers/__init__.py b/src/netbox_initializers/initializers/__init__.py index b7a2499..7db07dd 100644 --- a/src/netbox_initializers/initializers/__init__.py +++ b/src/netbox_initializers/initializers/__init__.py @@ -43,6 +43,7 @@ "vrfs", "aggregates", "virtual_machines", + "virtualization_disks", "virtualization_interfaces", "prefixes", "ip_addresses", @@ -112,13 +113,23 @@ def set_custom_fields_values(self, entity, custom_field_data): "Please check the 'on_objects' for that custom field in custom_fields.yml" ) elif key not in entity.custom_field_data: + # If the type is a object, we need to get its related type + if cf.type == "object": + model = cf.related_object_type.model_class() + value = model.objects.get(**value).id + + if cf.type == "multiobject": + yaml_value = value + model = cf.related_object_type.model_class() + value = [model.objects.get(**x).id for x in yaml_value] + entity.custom_field_data[key] = value save = True if missing_cfs: raise Exception( f"โš ๏ธ Custom field(s) '{missing_cfs}' requested for {entity} but not found in Netbox!" - "Please chceck the custom_fields.yml" + "Please check the custom_fields.yml" ) if save: @@ -213,6 +224,7 @@ def register_initializer(name: str, initializer): from .tenants import TenantInitializer from .users import UserInitializer from .virtual_machines import VirtualMachineInitializer +from .virtualization_disks import VMDiskInitializer from .virtualization_interfaces import VMInterfaceInitializer from .vlan_groups import VLANGroupInitializer from .vlans import VLANInitializer diff --git a/src/netbox_initializers/initializers/cables.py b/src/netbox_initializers/initializers/cables.py index ef4f32e..645adc1 100644 --- a/src/netbox_initializers/initializers/cables.py +++ b/src/netbox_initializers/initializers/cables.py @@ -231,23 +231,8 @@ def load_data(self): check_terminations_are_free(term_a, term_b) - cable = Cable.objects.create(**params) - - params_a_term = { - "termination_id": term_a.id, - "termination_type": term_a_ct, - "cable": cable, - "cable_end": "A", - } - CableTermination.objects.create(**params_a_term) - - params_b_term = { - "termination_id": term_b.id, - "termination_type": term_b_ct, - "cable": cable, - "cable_end": "B", - } - CableTermination.objects.create(**params_b_term) + cable = Cable(a_terminations=[term_a], b_terminations=[term_b], **params) + cable.save() print(f"๐Ÿงท Created cable {cable} {cable_name}") self.set_tags(cable, tags) diff --git a/src/netbox_initializers/initializers/custom_fields.py b/src/netbox_initializers/initializers/custom_fields.py index 7fa8307..9204ec2 100644 --- a/src/netbox_initializers/initializers/custom_fields.py +++ b/src/netbox_initializers/initializers/custom_fields.py @@ -25,6 +25,9 @@ def load_data(self): custom_field, created = CustomField.objects.get_or_create(name=cf_name) if created: + for object_type in cf_details.get("on_objects", []): + custom_field.object_types.add(get_class_for_class_path(object_type)) + if cf_details.get("default", False): custom_field.default = cf_details["default"] @@ -34,9 +37,6 @@ def load_data(self): if cf_details.get("label", False): custom_field.label = cf_details["label"] - for object_type in cf_details.get("on_objects", []): - custom_field.object_types.add(get_class_for_class_path(object_type)) - if cf_details.get("required", False): custom_field.required = cf_details["required"] @@ -52,8 +52,8 @@ def load_data(self): if cf_details.get("group_name", False): custom_field.group_name = cf_details["group_name"] - if cf_details.get("ui_visibility", False): - custom_field.ui_visibility = cf_details["ui_visibility"] + if cf_details.get("ui_visible", False): + custom_field.ui_visible = cf_details["ui_visible"] if cf_details.get("search_weight", -1) >= 0: custom_field.search_weight = cf_details["search_weight"] diff --git a/src/netbox_initializers/initializers/custom_links.py b/src/netbox_initializers/initializers/custom_links.py index 7b149f1..3efe0f3 100644 --- a/src/netbox_initializers/initializers/custom_links.py +++ b/src/netbox_initializers/initializers/custom_links.py @@ -8,7 +8,7 @@ def get_content_type(content_type): try: return ObjectType.objects.get(model=content_type) except ObjectType.DoesNotExist: - pass + print(f"โš ๏ธ The content_type '{content_type}' is unknown") return None @@ -20,13 +20,11 @@ def load_data(self): if custom_links is None: return for link in custom_links: - content_type_name = link.pop("content_type") - content_type = get_content_type(content_type_name) - if content_type is None: + content_types = [get_content_type(x) for x in link.pop("content_type")] + + if None in content_types: print( - "โš ๏ธ Unable to create Custom Link '{0}': The content_type '{1}' is unknown".format( - link.get("name"), content_type - ) + f"โš ๏ธ Unable to create Custom Link '{ link.get('name') }' due to unknown content_type" ) continue @@ -36,9 +34,9 @@ def load_data(self): ) if created: - custom_link.object_types.add(content_type) + custom_link.object_types.set(content_types) custom_link.save() - print("๐Ÿ”— Created Custom Link '{0}'".format(custom_link.name)) + print(f"๐Ÿ”— Created Custom Link '{custom_link.name}'") register_initializer("custom_links", CustomLinkInitializer) diff --git a/src/netbox_initializers/initializers/device_types.py b/src/netbox_initializers/initializers/device_types.py index 7ea87d5..f41cf02 100644 --- a/src/netbox_initializers/initializers/device_types.py +++ b/src/netbox_initializers/initializers/device_types.py @@ -1,6 +1,6 @@ from typing import List -from dcim.models import DeviceType, Manufacturer, Region +from dcim.models import DeviceType, Manufacturer, Platform, Region from dcim.models.device_component_templates import ( ConsolePortTemplate, ConsoleServerPortTemplate, @@ -18,7 +18,11 @@ MATCH_PARAMS = ["manufacturer", "model", "slug"] REQUIRED_ASSOCS = {"manufacturer": (Manufacturer, "name")} -OPTIONAL_ASSOCS = {"region": (Region, "name"), "tenant": (Tenant, "name")} +OPTIONAL_ASSOCS = { + "region": (Region, "name"), + "tenant": (Tenant, "name"), + "default_platform": (Platform, "name") +} NESTED_ASSOCS = {"rear_port": (RearPortTemplate, "name"), "power_port": (PowerPortTemplate, "name")} SUPPORTED_COMPONENTS = { "interfaces": (InterfaceTemplate, ["name"]), diff --git a/src/netbox_initializers/initializers/devices.py b/src/netbox_initializers/initializers/devices.py index c73a925..25733d6 100644 --- a/src/netbox_initializers/initializers/devices.py +++ b/src/netbox_initializers/initializers/devices.py @@ -1,6 +1,8 @@ from dcim.models import Device, DeviceRole, DeviceType, Location, Platform, Rack, Site +from django.apps import apps from extras.models import ConfigTemplate from tenancy.models import Tenant +from utilities.fields import CounterCacheField from virtualization.models import Cluster from . import BaseInitializer, register_initializer @@ -24,6 +26,19 @@ class DeviceInitializer(BaseInitializer): data_file_name = "devices.yml" + def recalculate_counters(self, device: Device): + counter_fields = [ + field for field in device._meta.get_fields() if type(field) is CounterCacheField + ] + + updated_values = {} + for field in counter_fields: + model = apps.get_model(field.to_model_name) + count = model.objects.filter(**{field.to_field_name: device}).count() + updated_values[field.name] = count + + Device.objects.filter(pk=device.pk).update(**updated_values) + def load_data(self): devices = self.load_yaml() if devices is None: @@ -35,8 +50,10 @@ def load_data(self): # primary ips are handled later in `380_primary_ips.py` params.pop("primary_ip4", None) params.pop("primary_ip6", None) + params.pop("oob_ip", None) params.pop("primary_ip4_vrf", None) params.pop("primary_ip6_vrf", None) + params.pop("oob_ip_vrf", None) for assoc, details in REQUIRED_ASSOCS.items(): model, field = details @@ -60,5 +77,8 @@ def load_data(self): self.set_custom_fields_values(device, custom_field_data) self.set_tags(device, tags) + # If we set any tags or custom fields, we need to recalculate all `CounterCacheField` + self.recalculate_counters(device) + register_initializer("devices", DeviceInitializer) diff --git a/src/netbox_initializers/initializers/locations.py b/src/netbox_initializers/initializers/locations.py index 30ffce6..20cbd1b 100644 --- a/src/netbox_initializers/initializers/locations.py +++ b/src/netbox_initializers/initializers/locations.py @@ -1,8 +1,9 @@ from dcim.models import Location, Site +from tenancy.models import Tenant from . import BaseInitializer, register_initializer -OPTIONAL_ASSOCS = {"site": (Site, "name"), "parent": (Location, "name")} +OPTIONAL_ASSOCS = {"site": (Site, "name"), "parent": (Location, "name"), "tenant": (Tenant, "name")} class LocationInitializer(BaseInitializer): diff --git a/src/netbox_initializers/initializers/primary_ips.py b/src/netbox_initializers/initializers/primary_ips.py index 8f576bc..754a2e3 100644 --- a/src/netbox_initializers/initializers/primary_ips.py +++ b/src/netbox_initializers/initializers/primary_ips.py @@ -7,6 +7,7 @@ OPTIONAL_ASSOCS = { "primary_ip4": (IPAddress, "address"), "primary_ip6": (IPAddress, "address"), + "oob_ip": (IPAddress, "address"), } @@ -26,7 +27,7 @@ def get_vrf_id(vrf_name): def link_primary_ip(assets, asset_model): for params in assets: - primary_ip_fields = set(params) & {"primary_ip4", "primary_ip6"} + primary_ip_fields = set(params) & {"primary_ip4", "primary_ip6", "oob_ip"} if not primary_ip_fields: continue diff --git a/src/netbox_initializers/initializers/sites.py b/src/netbox_initializers/initializers/sites.py index 0aac182..01e236c 100644 --- a/src/netbox_initializers/initializers/sites.py +++ b/src/netbox_initializers/initializers/sites.py @@ -56,7 +56,10 @@ def load_data(self): self.set_custom_fields_values(site, custom_field_data) self.set_tags(site, tags) + site_asns = site.asns.all() for asn in asnFounds: + if asn in site_asns: + continue site.asns.add(asn) print(" ๐Ÿ‘ค Assigned asn %s to site %s" % (asn, site.name)) diff --git a/src/netbox_initializers/initializers/virtualization_disks.py b/src/netbox_initializers/initializers/virtualization_disks.py new file mode 100644 index 0000000..488fcb6 --- /dev/null +++ b/src/netbox_initializers/initializers/virtualization_disks.py @@ -0,0 +1,36 @@ +from virtualization.models import VirtualDisk, VirtualMachine + +from . import BaseInitializer, register_initializer + +MATCH_PARAMS = ["name", "virtual_machine"] +REQUIRED_ASSOCS = {"virtual_machine": (VirtualMachine, "name")} + + +class VMDiskInitializer(BaseInitializer): + data_file_name = "virtualization_disks.yml" + + def load_data(self): + disks = self.load_yaml() + if disks is None: + return + for params in disks: + custom_field_data = self.pop_custom_fields(params) + tags = params.pop("tags", None) + + for assoc, details in REQUIRED_ASSOCS.items(): + model, field = details + query = {field: params.pop(assoc)} + + params[assoc] = model.objects.get(**query) + + matching_params, defaults = self.split_params(params, MATCH_PARAMS) + disk, created = VirtualDisk.objects.get_or_create(**matching_params, defaults=defaults) + + if created: + print("๐Ÿ’ฝ Created Disk", disk.name, disk.virtual_machine.name) + + self.set_custom_fields_values(disk, custom_field_data) + self.set_tags(disk, tags) + + +register_initializer("virtualization_disks", VMDiskInitializer) diff --git a/src/netbox_initializers/initializers/yaml/custom_fields.yml b/src/netbox_initializers/initializers/yaml/custom_fields.yml index e2b471d..5111cab 100644 --- a/src/netbox_initializers/initializers/yaml/custom_fields.yml +++ b/src/netbox_initializers/initializers/yaml/custom_fields.yml @@ -13,9 +13,9 @@ ## - disabled ## - loose ## - exact -## ui_visibility: -## - read-write -## - read-only +## ui_visible: +## - aways +## - if-set ## - hidden ## ## Examples: @@ -27,7 +27,7 @@ # required: false # weight: 0 # group_name: group1 -# ui_visibility: read-only +# ui_visible: if-set # search_weight: 100 # on_objects: # - dcim.models.Device diff --git a/src/netbox_initializers/initializers/yaml/custom_links.yml b/src/netbox_initializers/initializers/yaml/custom_links.yml index eb733ca..c738032 100644 --- a/src/netbox_initializers/initializers/yaml/custom_links.yml +++ b/src/netbox_initializers/initializers/yaml/custom_links.yml @@ -14,8 +14,11 @@ # link_url: 'https://github.com/netbox-community/netbox-docker' # new_window: False # content_type: device + # - name: link_to_localhost # link_text: 'Link to localhost' # link_url: 'http://localhost' # new_window: True -# content_type: device +# content_type: +# - device +# - site diff --git a/src/netbox_initializers/initializers/yaml/devices.yml b/src/netbox_initializers/initializers/yaml/devices.yml index 4e7155d..59deeda 100644 --- a/src/netbox_initializers/initializers/yaml/devices.yml +++ b/src/netbox_initializers/initializers/yaml/devices.yml @@ -33,6 +33,8 @@ # primary_ip4_vrf: vrf1 # primary_ip6: 2001:db8:a000:1::2/64 # primary_ip6_vrf: vrf1 +# oob_ip: 192.168.100.2/24 +# oob_ip_vrf: vrf2 # custom_field_data: # text_field: Description # - name: server03 diff --git a/src/netbox_initializers/initializers/yaml/virtualization_disks.yml b/src/netbox_initializers/initializers/yaml/virtualization_disks.yml new file mode 100644 index 0000000..f48b454 --- /dev/null +++ b/src/netbox_initializers/initializers/yaml/virtualization_disks.yml @@ -0,0 +1,9 @@ +# - name: Virtual Disk 1 +# description: Virtual Disk 1 +# size: 256 +# virtual_machine: virtual machine 1 + +# - name: Virtual Disk 2 +# description: Virtual Disk 2 +# size: 1024 +# virtual_machine: virtual machine 1