From 81d9e4f5602dd5a9b81bf4bb2c2d8e676b73222d Mon Sep 17 00:00:00 2001 From: kr3ator <48438188+kr3ator@users.noreply.github.com> Date: Tue, 5 Apr 2022 08:45:22 +0200 Subject: [PATCH 1/9] Fix setting CF data if CF object is missing --- startup_scripts/080_tenants.py | 4 +-- startup_scripts/110_sites.py | 4 +-- startup_scripts/140_racks.py | 4 +-- startup_scripts/150_power_panels.py | 4 +-- startup_scripts/160_power_feeds.py | 4 +-- startup_scripts/190_device_types.py | 4 +-- startup_scripts/200_devices.py | 4 +-- startup_scripts/210_dcim_interfaces.py | 4 +-- startup_scripts/230_route_targets.py | 4 +-- startup_scripts/240_vrfs.py | 4 +-- startup_scripts/270_aggregates.py | 4 +-- startup_scripts/310_clusters.py | 4 +-- startup_scripts/320_vlan_groups.py | 4 +-- startup_scripts/330_vlans.py | 4 +-- startup_scripts/340_virtual_machines.py | 4 +-- .../350_virtualization_interfaces.py | 4 +-- startup_scripts/360_prefixes.py | 4 +-- startup_scripts/370_ip_addresses.py | 4 +-- startup_scripts/420_providers.py | 4 +-- startup_scripts/440_circuit_types.py | 4 +-- startup_scripts/450_circuits.py | 4 +-- .../startup_script_utils/custom_fields.py | 33 +++++++++++++++++-- 22 files changed, 73 insertions(+), 44 deletions(-) diff --git a/startup_scripts/080_tenants.py b/startup_scripts/080_tenants.py index 7b1a629f0..98e24a615 100644 --- a/startup_scripts/080_tenants.py +++ b/startup_scripts/080_tenants.py @@ -23,6 +23,6 @@ tenant, created = Tenant.objects.get_or_create(**params) if created: - set_custom_fields_values(tenant, custom_field_data) - print("πŸ‘©β€πŸ’» Created Tenant", tenant.name) + + set_custom_fields_values(tenant, custom_field_data) diff --git a/startup_scripts/110_sites.py b/startup_scripts/110_sites.py index f7851391a..736a61c7b 100644 --- a/startup_scripts/110_sites.py +++ b/startup_scripts/110_sites.py @@ -24,6 +24,6 @@ site, created = Site.objects.get_or_create(**params) if created: - set_custom_fields_values(site, custom_field_data) - print("πŸ“ Created site", site.name) + + set_custom_fields_values(site, custom_field_data) diff --git a/startup_scripts/140_racks.py b/startup_scripts/140_racks.py index b2cfc801c..a49ea01ae 100644 --- a/startup_scripts/140_racks.py +++ b/startup_scripts/140_racks.py @@ -36,6 +36,6 @@ rack, created = Rack.objects.get_or_create(**params) if created: - set_custom_fields_values(rack, custom_field_data) - print("πŸ”³ Created rack", rack.site, rack.name) + + set_custom_fields_values(rack, custom_field_data) diff --git a/startup_scripts/150_power_panels.py b/startup_scripts/150_power_panels.py index 8542435bc..e0bcecac1 100644 --- a/startup_scripts/150_power_panels.py +++ b/startup_scripts/150_power_panels.py @@ -31,6 +31,6 @@ power_panel, created = PowerPanel.objects.get_or_create(**params) if created: - set_custom_fields_values(power_panel, custom_field_data) - print("⚑ Created Power Panel", power_panel.site, power_panel.name) + + set_custom_fields_values(power_panel, custom_field_data) diff --git a/startup_scripts/160_power_feeds.py b/startup_scripts/160_power_feeds.py index f5aa5b570..7ef55b0e2 100644 --- a/startup_scripts/160_power_feeds.py +++ b/startup_scripts/160_power_feeds.py @@ -31,6 +31,6 @@ power_feed, created = PowerFeed.objects.get_or_create(**params) if created: - set_custom_fields_values(power_feed, custom_field_data) - print("⚑ Created Power Feed", power_feed.name) + + set_custom_fields_values(power_feed, custom_field_data) diff --git a/startup_scripts/190_device_types.py b/startup_scripts/190_device_types.py index 0d3050b45..c43f632e9 100644 --- a/startup_scripts/190_device_types.py +++ b/startup_scripts/190_device_types.py @@ -32,6 +32,6 @@ device_type, created = DeviceType.objects.get_or_create(**params) if created: - set_custom_fields_values(device_type, custom_field_data) - print("πŸ”‘ Created device type", device_type.manufacturer, device_type.model) + + set_custom_fields_values(device_type, custom_field_data) diff --git a/startup_scripts/200_devices.py b/startup_scripts/200_devices.py index 423b7c9de..ae92926bf 100644 --- a/startup_scripts/200_devices.py +++ b/startup_scripts/200_devices.py @@ -47,6 +47,6 @@ device, created = Device.objects.get_or_create(**params) if created: - set_custom_fields_values(device, custom_field_data) - print("πŸ–₯️ Created device", device.name) + + set_custom_fields_values(device, custom_field_data) diff --git a/startup_scripts/210_dcim_interfaces.py b/startup_scripts/210_dcim_interfaces.py index a8026282f..880db912e 100644 --- a/startup_scripts/210_dcim_interfaces.py +++ b/startup_scripts/210_dcim_interfaces.py @@ -22,6 +22,6 @@ interface, created = Interface.objects.get_or_create(**params) if created: - set_custom_fields_values(interface, custom_field_data) - print("🧷 Created interface", interface.name, interface.device.name) + + set_custom_fields_values(interface, custom_field_data) diff --git a/startup_scripts/230_route_targets.py b/startup_scripts/230_route_targets.py index e1c82217e..cb6721b77 100644 --- a/startup_scripts/230_route_targets.py +++ b/startup_scripts/230_route_targets.py @@ -24,6 +24,6 @@ route_target, created = RouteTarget.objects.get_or_create(**params) if created: - set_custom_fields_values(route_target, custom_field_data) - print("🎯 Created Route Target", route_target.name) + + set_custom_fields_values(route_target, custom_field_data) diff --git a/startup_scripts/240_vrfs.py b/startup_scripts/240_vrfs.py index a67c8c8cb..4cf9f163a 100644 --- a/startup_scripts/240_vrfs.py +++ b/startup_scripts/240_vrfs.py @@ -24,6 +24,6 @@ vrf, created = VRF.objects.get_or_create(**params) if created: - set_custom_fields_values(vrf, custom_field_data) - print("πŸ“¦ Created VRF", vrf.name) + + set_custom_fields_values(vrf, custom_field_data) diff --git a/startup_scripts/270_aggregates.py b/startup_scripts/270_aggregates.py index c638e6f81..1cc7b5e86 100644 --- a/startup_scripts/270_aggregates.py +++ b/startup_scripts/270_aggregates.py @@ -37,6 +37,6 @@ aggregate, created = Aggregate.objects.get_or_create(**params) if created: - set_custom_fields_values(aggregate, custom_field_data) - print("πŸ—žοΈ Created Aggregate", aggregate.prefix) + + set_custom_fields_values(aggregate, custom_field_data) diff --git a/startup_scripts/310_clusters.py b/startup_scripts/310_clusters.py index 2748f2038..e4923518a 100644 --- a/startup_scripts/310_clusters.py +++ b/startup_scripts/310_clusters.py @@ -37,6 +37,6 @@ cluster, created = Cluster.objects.get_or_create(**params) if created: - set_custom_fields_values(cluster, custom_field_data) - print("πŸ—„οΈ Created cluster", cluster.name) + + set_custom_fields_values(cluster, custom_field_data) diff --git a/startup_scripts/320_vlan_groups.py b/startup_scripts/320_vlan_groups.py index 2a4a33dec..7b5fe6db4 100644 --- a/startup_scripts/320_vlan_groups.py +++ b/startup_scripts/320_vlan_groups.py @@ -35,6 +35,6 @@ vlan_group, created = VLANGroup.objects.get_or_create(**params) if created: - set_custom_fields_values(vlan_group, custom_field_data) - print("🏘️ Created VLAN Group", vlan_group.name) + + set_custom_fields_values(vlan_group, custom_field_data) diff --git a/startup_scripts/330_vlans.py b/startup_scripts/330_vlans.py index e8ebb94f1..8a6b0c84e 100644 --- a/startup_scripts/330_vlans.py +++ b/startup_scripts/330_vlans.py @@ -31,6 +31,6 @@ vlan, created = VLAN.objects.get_or_create(**params) if created: - set_custom_fields_values(vlan, custom_field_data) - print("🏠 Created VLAN", vlan.name) + + set_custom_fields_values(vlan, custom_field_data) diff --git a/startup_scripts/340_virtual_machines.py b/startup_scripts/340_virtual_machines.py index 2e3f42885..94d4fefab 100644 --- a/startup_scripts/340_virtual_machines.py +++ b/startup_scripts/340_virtual_machines.py @@ -41,6 +41,6 @@ virtual_machine, created = VirtualMachine.objects.get_or_create(**params) if created: - set_custom_fields_values(virtual_machine, custom_field_data) - print("πŸ–₯️ Created virtual machine", virtual_machine.name) + + set_custom_fields_values(virtual_machine, custom_field_data) diff --git a/startup_scripts/350_virtualization_interfaces.py b/startup_scripts/350_virtualization_interfaces.py index 6ee63474c..0ae5c5c8a 100644 --- a/startup_scripts/350_virtualization_interfaces.py +++ b/startup_scripts/350_virtualization_interfaces.py @@ -22,6 +22,6 @@ interface, created = VMInterface.objects.get_or_create(**params) if created: - set_custom_fields_values(interface, custom_field_data) - print("🧷 Created interface", interface.name, interface.virtual_machine.name) + + set_custom_fields_values(interface, custom_field_data) diff --git a/startup_scripts/360_prefixes.py b/startup_scripts/360_prefixes.py index 4e2b0d007..c4cb93659 100644 --- a/startup_scripts/360_prefixes.py +++ b/startup_scripts/360_prefixes.py @@ -34,6 +34,6 @@ prefix, created = Prefix.objects.get_or_create(**params) if created: - set_custom_fields_values(prefix, custom_field_data) - print("πŸ“Œ Created Prefix", prefix.prefix) + + set_custom_fields_values(prefix, custom_field_data) diff --git a/startup_scripts/370_ip_addresses.py b/startup_scripts/370_ip_addresses.py index 7f166f0bb..76a716908 100644 --- a/startup_scripts/370_ip_addresses.py +++ b/startup_scripts/370_ip_addresses.py @@ -58,6 +58,6 @@ ip_address, created = IPAddress.objects.get_or_create(**params) if created: - set_custom_fields_values(ip_address, custom_field_data) - print("🧬 Created IP Address", ip_address.address) + + set_custom_fields_values(ip_address, custom_field_data) diff --git a/startup_scripts/420_providers.py b/startup_scripts/420_providers.py index 5c4330ad3..1c652eb9e 100644 --- a/startup_scripts/420_providers.py +++ b/startup_scripts/420_providers.py @@ -14,6 +14,6 @@ provider, created = Provider.objects.get_or_create(**params) if created: - set_custom_fields_values(provider, custom_field_data) - print("πŸ“‘ Created provider", provider.name) + + set_custom_fields_values(provider, custom_field_data) diff --git a/startup_scripts/440_circuit_types.py b/startup_scripts/440_circuit_types.py index 071793c6f..fa49681c3 100644 --- a/startup_scripts/440_circuit_types.py +++ b/startup_scripts/440_circuit_types.py @@ -14,6 +14,6 @@ circuit_type, created = CircuitType.objects.get_or_create(**params) if created: - set_custom_fields_values(circuit_type, custom_field_data) - print("⚑ Created Circuit Type", circuit_type.name) + + set_custom_fields_values(circuit_type, custom_field_data) diff --git a/startup_scripts/450_circuits.py b/startup_scripts/450_circuits.py index f82d3b71d..c1822039e 100644 --- a/startup_scripts/450_circuits.py +++ b/startup_scripts/450_circuits.py @@ -32,6 +32,6 @@ circuit, created = Circuit.objects.get_or_create(**params) if created: - set_custom_fields_values(circuit, custom_field_data) - print("⚑ Created Circuit", circuit.cid) + + set_custom_fields_values(circuit, custom_field_data) diff --git a/startup_scripts/startup_script_utils/custom_fields.py b/startup_scripts/startup_script_utils/custom_fields.py index c00097079..74e8a2501 100644 --- a/startup_scripts/startup_script_utils/custom_fields.py +++ b/startup_scripts/startup_script_utils/custom_fields.py @@ -1,9 +1,38 @@ +from django.contrib.contenttypes.models import ContentType +from django.core.exceptions import ObjectDoesNotExist +from extras.models import CustomField + + def set_custom_fields_values(entity, custom_field_data): if not custom_field_data: return - entity.custom_field_data = custom_field_data - return entity.save() + missing_cfs = [] + save = False + for key, value in custom_field_data.items(): + try: + cf = CustomField.objects.get(name=key) + except ObjectDoesNotExist: + missing_cfs.append(key) + else: + ct = ContentType.objects.get_for_model(entity) + if ct not in cf.content_types.all(): + print( + f"⚠️ Custom field {key} is not enabled for {entity}'s model!" + "Please check the 'on_objects' for that custom field in custom_fields.yml" + ) + elif key not in entity.custom_field_data: + 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" + ) + + if save: + entity.save() def pop_custom_fields(params): From d1c69e8fe5e0e140ce5edb2a47a07e8de8bb8646 Mon Sep 17 00:00:00 2001 From: kr3ator <48438188+kr3ator@users.noreply.github.com> Date: Wed, 6 Apr 2022 16:45:25 +0200 Subject: [PATCH 2/9] fix: invalid Interface optional assoc --- startup_scripts/370_ip_addresses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/startup_scripts/370_ip_addresses.py b/startup_scripts/370_ip_addresses.py index 76a716908..afe761214 100644 --- a/startup_scripts/370_ip_addresses.py +++ b/startup_scripts/370_ip_addresses.py @@ -17,7 +17,7 @@ optional_assocs = { "tenant": (Tenant, "name"), "vrf": (VRF, "name"), - "interface": (None, None), + "interface": (Interface, "name"), } vm_interface_ct = ContentType.objects.filter( From d5b1d9ce39ea0b78dff78b1118c35ba662baec92 Mon Sep 17 00:00:00 2001 From: Robin Beismann Date: Thu, 7 Apr 2022 16:09:27 +0200 Subject: [PATCH 3/9] Added environment variable for CSRF_TRUSTED_ORIGINS --- configuration/configuration.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/configuration/configuration.py b/configuration/configuration.py index 1db605111..7bfa6a6c1 100644 --- a/configuration/configuration.py +++ b/configuration/configuration.py @@ -120,6 +120,11 @@ def _read_secret(secret_name, default = None): CORS_ORIGIN_WHITELIST = list(filter(None, environ.get('CORS_ORIGIN_WHITELIST', 'https://localhost').split(' '))) CORS_ORIGIN_REGEX_WHITELIST = [re.compile(r) for r in list(filter(None, environ.get('CORS_ORIGIN_REGEX_WHITELIST', '').split(' ')))] +# Cross-Site-Request-Forgery-Attack settings. If Netbox is sitting behind a reverse proxy, you might need to set the CSRF_TRUSTED_ORIGINS flag. +# Django 4.0 requires to specify the URL Scheme in this setting. An example environment variable could be specified like: +# CSRF_TRUSTED_ORIGINS=https://demo.netbox.dev http://demo.netbox.dev +CSRF_TRUSTED_ORIGINS = list(filter(None, environ.get('CSRF_TRUSTED_ORIGINS', 'https://localhost').split(' '))) + # Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal # sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging # on a production system. From 9be7b0e109a8cd0b4a959cc7a903b36b60eaab10 Mon Sep 17 00:00:00 2001 From: kr3ator <48438188+kr3ator@users.noreply.github.com> Date: Tue, 5 Apr 2022 08:34:08 +0200 Subject: [PATCH 4/9] feat: Make startup scripts idempotent --- initializers/users.yml | 1 + startup_scripts/000_users.py | 20 +++++++++++-------- startup_scripts/020_object_permissions.py | 8 +++++--- startup_scripts/040_custom_links.py | 6 ++++-- startup_scripts/050_tags.py | 5 +++-- startup_scripts/060_webhooks.py | 6 ++++-- startup_scripts/070_tenant_groups.py | 5 +++-- startup_scripts/080_tenants.py | 10 ++++++++-- startup_scripts/090_regions.py | 5 +++-- startup_scripts/110_sites.py | 10 ++++++++-- startup_scripts/120_locations.py | 6 ++++-- startup_scripts/130_rack_roles.py | 5 +++-- startup_scripts/140_racks.py | 12 ++++++++--- startup_scripts/150_power_panels.py | 12 ++++++++--- startup_scripts/160_power_feeds.py | 12 ++++++++--- startup_scripts/170_manufacturers.py | 5 +++-- startup_scripts/180_device_roles.py | 5 +++-- startup_scripts/190_device_types.py | 12 ++++++++--- startup_scripts/200_devices.py | 14 +++++++++---- startup_scripts/210_dcim_interfaces.py | 11 ++++++++-- startup_scripts/220_platforms.py | 5 +++-- startup_scripts/230_route_targets.py | 10 ++++++++-- startup_scripts/240_vrfs.py | 11 ++++++++-- startup_scripts/250_rirs.py | 5 +++-- startup_scripts/260_asns.py | 7 ++++--- startup_scripts/270_aggregates.py | 12 ++++++++--- startup_scripts/280_prefix_vlan_roles.py | 5 +++-- startup_scripts/290_cluster_types.py | 5 +++-- startup_scripts/300_cluster_groups.py | 7 +++++-- startup_scripts/310_clusters.py | 12 ++++++++--- startup_scripts/320_vlan_groups.py | 11 ++++++++-- startup_scripts/330_vlans.py | 11 ++++++++-- startup_scripts/340_virtual_machines.py | 14 ++++++++++--- .../350_virtualization_interfaces.py | 11 ++++++++-- startup_scripts/360_prefixes.py | 11 ++++++++-- startup_scripts/370_ip_addresses.py | 11 ++++++++-- startup_scripts/400_services.py | 4 +++- startup_scripts/420_providers.py | 10 ++++++++-- startup_scripts/440_circuit_types.py | 10 ++++++++-- startup_scripts/450_circuits.py | 12 ++++++++--- .../startup_script_utils/__init__.py | 1 + startup_scripts/startup_script_utils/utils.py | 15 ++++++++++++++ 42 files changed, 275 insertions(+), 95 deletions(-) create mode 100644 startup_scripts/startup_script_utils/utils.py diff --git a/initializers/users.yml b/initializers/users.yml index c163d5043..2dca92d6f 100644 --- a/initializers/users.yml +++ b/initializers/users.yml @@ -4,6 +4,7 @@ # password: reader # writer: # password: writer +# api_token: # leave empty to not generate any token # jdoe: # first_name: John # last_name: Doe diff --git a/startup_scripts/000_users.py b/startup_scripts/000_users.py index 1435d8128..7ca63052f 100644 --- a/startup_scripts/000_users.py +++ b/startup_scripts/000_users.py @@ -9,13 +9,17 @@ sys.exit() for username, user_details in users.items(): - if not User.objects.filter(username=username): - user = User.objects.create_user( - username=username, - password=user_details.get("password", 0) or User.objects.make_random_password(), - ) - print("πŸ‘€ Created user", username) + api_token = user_details.pop("api_token", Token.generate_key()) + password = user_details.pop("password", User.objects.make_random_password()) + + user, created = User.objects.get_or_create(username=username, defaults=user_details) + + if created: + user.set_password(password) + user.save() - if user_details.get("api_token", 0): - Token.objects.create(user=user, key=user_details["api_token"]) + if api_token: + Token.objects.get_or_create(user=user, key=api_token) + + print("πŸ‘€ Created user", username) diff --git a/startup_scripts/020_object_permissions.py b/startup_scripts/020_object_permissions.py index 8a5ecd40b..562a09c26 100644 --- a/startup_scripts/020_object_permissions.py +++ b/startup_scripts/020_object_permissions.py @@ -14,9 +14,11 @@ object_permission, created = ObjectPermission.objects.get_or_create( name=permission_name, - description=permission_details["description"], - enabled=permission_details["enabled"], - actions=permission_details["actions"], + defaults={ + "description": permission_details["description"], + "enabled": permission_details["enabled"], + "actions": permission_details["actions"], + }, ) if permission_details.get("object_types", 0): diff --git a/startup_scripts/040_custom_links.py b/startup_scripts/040_custom_links.py index 40144bd13..d8c0bba32 100644 --- a/startup_scripts/040_custom_links.py +++ b/startup_scripts/040_custom_links.py @@ -2,7 +2,7 @@ from django.contrib.contenttypes.models import ContentType from extras.models import CustomLink -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params custom_links = load_yaml("/opt/netbox/initializers/custom_links.yml") @@ -28,6 +28,8 @@ def get_content_type_id(content_type): ) continue - custom_link, created = CustomLink.objects.get_or_create(**link) + matching_params, defaults = split_params(link) + custom_link, created = CustomLink.objects.get_or_create(**matching_params, defaults=defaults) + if created: print("πŸ”— Created Custom Link '{0}'".format(custom_link.name)) diff --git a/startup_scripts/050_tags.py b/startup_scripts/050_tags.py index e50a000cc..06a18cb9f 100644 --- a/startup_scripts/050_tags.py +++ b/startup_scripts/050_tags.py @@ -1,7 +1,7 @@ import sys from extras.models import Tag -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params from utilities.choices import ColorChoices tags = load_yaml("/opt/netbox/initializers/tags.yml") @@ -17,7 +17,8 @@ if color in color_tpl: params["color"] = color_tpl[0] - tag, created = Tag.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + tag, created = Tag.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🎨 Created Tag", tag.name) diff --git a/startup_scripts/060_webhooks.py b/startup_scripts/060_webhooks.py index 8787f5f40..9de6e7e31 100644 --- a/startup_scripts/060_webhooks.py +++ b/startup_scripts/060_webhooks.py @@ -2,7 +2,7 @@ from django.contrib.contenttypes.models import ContentType from extras.models import Webhook -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params webhooks = load_yaml("/opt/netbox/initializers/webhooks.yml") @@ -26,7 +26,9 @@ def get_content_type_id(hook_name, content_type): except ContentType.DoesNotExist: continue - webhook, created = Webhook.objects.get_or_create(**hook) + matching_params, defaults = split_params(hook) + webhook, created = Webhook.objects.get_or_create(**matching_params, defaults=defaults) + if created: webhook.content_types.set(obj_type_ids) webhook.save() diff --git a/startup_scripts/070_tenant_groups.py b/startup_scripts/070_tenant_groups.py index 65cf15541..0bfebadbe 100644 --- a/startup_scripts/070_tenant_groups.py +++ b/startup_scripts/070_tenant_groups.py @@ -1,6 +1,6 @@ import sys -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params from tenancy.models import TenantGroup tenant_groups = load_yaml("/opt/netbox/initializers/tenant_groups.yml") @@ -9,7 +9,8 @@ sys.exit() for params in tenant_groups: - tenant_group, created = TenantGroup.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + tenant_group, created = TenantGroup.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ”³ Created Tenant Group", tenant_group.name) diff --git a/startup_scripts/080_tenants.py b/startup_scripts/080_tenants.py index 98e24a615..88d68b7b7 100644 --- a/startup_scripts/080_tenants.py +++ b/startup_scripts/080_tenants.py @@ -1,6 +1,11 @@ import sys -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant, TenantGroup tenants = load_yaml("/opt/netbox/initializers/tenants.yml") @@ -20,7 +25,8 @@ params[assoc] = model.objects.get(**query) - tenant, created = Tenant.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + tenant, created = Tenant.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ‘©β€πŸ’» Created Tenant", tenant.name) diff --git a/startup_scripts/090_regions.py b/startup_scripts/090_regions.py index 9d5c91f92..592c4c5ac 100644 --- a/startup_scripts/090_regions.py +++ b/startup_scripts/090_regions.py @@ -1,7 +1,7 @@ import sys from dcim.models import Region -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params regions = load_yaml("/opt/netbox/initializers/regions.yml") @@ -19,7 +19,8 @@ params[assoc] = model.objects.get(**query) - region, created = Region.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + region, created = Region.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🌐 Created region", region.name) diff --git a/startup_scripts/110_sites.py b/startup_scripts/110_sites.py index 736a61c7b..c310e1423 100644 --- a/startup_scripts/110_sites.py +++ b/startup_scripts/110_sites.py @@ -1,7 +1,12 @@ import sys from dcim.models import Region, Site -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant sites = load_yaml("/opt/netbox/initializers/sites.yml") @@ -21,7 +26,8 @@ params[assoc] = model.objects.get(**query) - site, created = Site.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + site, created = Site.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ“ Created site", site.name) diff --git a/startup_scripts/120_locations.py b/startup_scripts/120_locations.py index d8a2c5a11..6a269b24e 100644 --- a/startup_scripts/120_locations.py +++ b/startup_scripts/120_locations.py @@ -1,13 +1,14 @@ import sys from dcim.models import Location, Site -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params rack_groups = load_yaml("/opt/netbox/initializers/locations.yml") if rack_groups is None: sys.exit() +match_params = ["name", "slug", "site"] required_assocs = {"site": (Site, "name")} for params in rack_groups: @@ -17,7 +18,8 @@ query = {field: params.pop(assoc)} params[assoc] = model.objects.get(**query) - location, created = Location.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + location, created = Location.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🎨 Created location", location.name) diff --git a/startup_scripts/130_rack_roles.py b/startup_scripts/130_rack_roles.py index 585040558..8d43237be 100644 --- a/startup_scripts/130_rack_roles.py +++ b/startup_scripts/130_rack_roles.py @@ -1,7 +1,7 @@ import sys from dcim.models import RackRole -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params from utilities.choices import ColorChoices rack_roles = load_yaml("/opt/netbox/initializers/rack_roles.yml") @@ -17,7 +17,8 @@ if color in color_tpl: params["color"] = color_tpl[0] - rack_role, created = RackRole.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + rack_role, created = RackRole.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🎨 Created rack role", rack_role.name) diff --git a/startup_scripts/140_racks.py b/startup_scripts/140_racks.py index a49ea01ae..f3dc1452b 100644 --- a/startup_scripts/140_racks.py +++ b/startup_scripts/140_racks.py @@ -1,7 +1,12 @@ import sys from dcim.models import Location, Rack, RackRole, Site -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant racks = load_yaml("/opt/netbox/initializers/racks.yml") @@ -9,8 +14,8 @@ if racks is None: sys.exit() +match_params = ["name", "site"] required_assocs = {"site": (Site, "name")} - optional_assocs = { "role": (RackRole, "name"), "tenant": (Tenant, "name"), @@ -33,7 +38,8 @@ params[assoc] = model.objects.get(**query) - rack, created = Rack.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + rack, created = Rack.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ”³ Created rack", rack.site, rack.name) diff --git a/startup_scripts/150_power_panels.py b/startup_scripts/150_power_panels.py index e0bcecac1..1397a9392 100644 --- a/startup_scripts/150_power_panels.py +++ b/startup_scripts/150_power_panels.py @@ -1,15 +1,20 @@ import sys from dcim.models import Location, PowerPanel, Site -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) power_panels = load_yaml("/opt/netbox/initializers/power_panels.yml") if power_panels is None: sys.exit() +match_params = ["name", "site"] required_assocs = {"site": (Site, "name")} - optional_assocs = {"location": (Location, "name")} for params in power_panels: @@ -28,7 +33,8 @@ params[assoc] = model.objects.get(**query) - power_panel, created = PowerPanel.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + power_panel, created = PowerPanel.objects.get_or_create(**matching_params, defaults=defaults) if created: print("⚑ Created Power Panel", power_panel.site, power_panel.name) diff --git a/startup_scripts/160_power_feeds.py b/startup_scripts/160_power_feeds.py index 7ef55b0e2..7b3e6acef 100644 --- a/startup_scripts/160_power_feeds.py +++ b/startup_scripts/160_power_feeds.py @@ -1,15 +1,20 @@ import sys from dcim.models import PowerFeed, PowerPanel, Rack -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) power_feeds = load_yaml("/opt/netbox/initializers/power_feeds.yml") if power_feeds is None: sys.exit() +match_params = ["name", "power_panel"] required_assocs = {"power_panel": (PowerPanel, "name")} - optional_assocs = {"rack": (Rack, "name")} for params in power_feeds: @@ -28,7 +33,8 @@ params[assoc] = model.objects.get(**query) - power_feed, created = PowerFeed.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + power_feed, created = PowerFeed.objects.get_or_create(**matching_params, defaults=defaults) if created: print("⚑ Created Power Feed", power_feed.name) diff --git a/startup_scripts/170_manufacturers.py b/startup_scripts/170_manufacturers.py index d11b44092..c105a4651 100644 --- a/startup_scripts/170_manufacturers.py +++ b/startup_scripts/170_manufacturers.py @@ -1,7 +1,7 @@ import sys from dcim.models import Manufacturer -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params manufacturers = load_yaml("/opt/netbox/initializers/manufacturers.yml") @@ -9,7 +9,8 @@ sys.exit() for params in manufacturers: - manufacturer, created = Manufacturer.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + manufacturer, created = Manufacturer.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🏭 Created Manufacturer", manufacturer.name) diff --git a/startup_scripts/180_device_roles.py b/startup_scripts/180_device_roles.py index 635acff54..3cea0f10d 100644 --- a/startup_scripts/180_device_roles.py +++ b/startup_scripts/180_device_roles.py @@ -1,7 +1,7 @@ import sys from dcim.models import DeviceRole -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params from utilities.choices import ColorChoices device_roles = load_yaml("/opt/netbox/initializers/device_roles.yml") @@ -18,7 +18,8 @@ if color in color_tpl: params["color"] = color_tpl[0] - device_role, created = DeviceRole.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + device_role, created = DeviceRole.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🎨 Created device role", device_role.name) diff --git a/startup_scripts/190_device_types.py b/startup_scripts/190_device_types.py index c43f632e9..98d7f5b4b 100644 --- a/startup_scripts/190_device_types.py +++ b/startup_scripts/190_device_types.py @@ -1,7 +1,12 @@ import sys from dcim.models import DeviceType, Manufacturer, Region -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant device_types = load_yaml("/opt/netbox/initializers/device_types.yml") @@ -9,8 +14,8 @@ if device_types is None: sys.exit() +match_params = ["manufacturer", "model", "slug"] required_assocs = {"manufacturer": (Manufacturer, "name")} - optional_assocs = {"region": (Region, "name"), "tenant": (Tenant, "name")} for params in device_types: @@ -29,7 +34,8 @@ params[assoc] = model.objects.get(**query) - device_type, created = DeviceType.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + device_type, created = DeviceType.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ”‘ Created device type", device_type.manufacturer, device_type.model) diff --git a/startup_scripts/200_devices.py b/startup_scripts/200_devices.py index ae92926bf..189471ca8 100644 --- a/startup_scripts/200_devices.py +++ b/startup_scripts/200_devices.py @@ -1,7 +1,12 @@ import sys from dcim.models import Device, DeviceRole, DeviceType, Location, Platform, Rack, Site -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant from virtualization.models import Cluster @@ -10,12 +15,12 @@ if devices is None: sys.exit() +match_params = ["device_type", "name", "site"] required_assocs = { "device_role": (DeviceRole, "name"), "device_type": (DeviceType, "model"), "site": (Site, "name"), } - optional_assocs = { "tenant": (Tenant, "name"), "platform": (Platform, "name"), @@ -27,7 +32,7 @@ for params in devices: custom_field_data = pop_custom_fields(params) - # primary ips are handled later in `270_primary_ips.py` + # primary ips are handled later in `380_primary_ips.py` params.pop("primary_ip4", None) params.pop("primary_ip6", None) @@ -44,7 +49,8 @@ params[assoc] = model.objects.get(**query) - device, created = Device.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + device, created = Device.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ–₯️ Created device", device.name) diff --git a/startup_scripts/210_dcim_interfaces.py b/startup_scripts/210_dcim_interfaces.py index 880db912e..b217f3b04 100644 --- a/startup_scripts/210_dcim_interfaces.py +++ b/startup_scripts/210_dcim_interfaces.py @@ -1,13 +1,19 @@ import sys from dcim.models import Device, Interface -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) interfaces = load_yaml("/opt/netbox/initializers/dcim_interfaces.yml") if interfaces is None: sys.exit() +match_params = ["device", "name"] required_assocs = {"device": (Device, "name")} for params in interfaces: @@ -19,7 +25,8 @@ params[assoc] = model.objects.get(**query) - interface, created = Interface.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + interface, created = Interface.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🧷 Created interface", interface.name, interface.device.name) diff --git a/startup_scripts/220_platforms.py b/startup_scripts/220_platforms.py index 633b89ffc..73905d4e1 100644 --- a/startup_scripts/220_platforms.py +++ b/startup_scripts/220_platforms.py @@ -1,7 +1,7 @@ import sys from dcim.models import Manufacturer, Platform -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params platforms = load_yaml("/opt/netbox/initializers/platforms.yml") @@ -21,7 +21,8 @@ params[assoc] = model.objects.get(**query) - platform, created = Platform.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + platform, created = Platform.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ’Ύ Created platform", platform.name) diff --git a/startup_scripts/230_route_targets.py b/startup_scripts/230_route_targets.py index cb6721b77..12ca9a591 100644 --- a/startup_scripts/230_route_targets.py +++ b/startup_scripts/230_route_targets.py @@ -1,7 +1,12 @@ import sys from ipam.models import RouteTarget -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant route_targets = load_yaml("/opt/netbox/initializers/route_targets.yml") @@ -21,7 +26,8 @@ params[assoc] = model.objects.get(**query) - route_target, created = RouteTarget.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + route_target, created = RouteTarget.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🎯 Created Route Target", route_target.name) diff --git a/startup_scripts/240_vrfs.py b/startup_scripts/240_vrfs.py index 4cf9f163a..1b4cd1a97 100644 --- a/startup_scripts/240_vrfs.py +++ b/startup_scripts/240_vrfs.py @@ -1,7 +1,12 @@ import sys from ipam.models import VRF -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant vrfs = load_yaml("/opt/netbox/initializers/vrfs.yml") @@ -9,6 +14,7 @@ if vrfs is None: sys.exit() +match_params = ["name", "rd"] optional_assocs = {"tenant": (Tenant, "name")} for params in vrfs: @@ -21,7 +27,8 @@ params[assoc] = model.objects.get(**query) - vrf, created = VRF.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + vrf, created = VRF.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ“¦ Created VRF", vrf.name) diff --git a/startup_scripts/250_rirs.py b/startup_scripts/250_rirs.py index 0e0df2008..08d1703af 100644 --- a/startup_scripts/250_rirs.py +++ b/startup_scripts/250_rirs.py @@ -1,7 +1,7 @@ import sys from ipam.models import RIR -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params rirs = load_yaml("/opt/netbox/initializers/rirs.yml") @@ -9,7 +9,8 @@ sys.exit() for params in rirs: - rir, created = RIR.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + rir, created = RIR.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ—ΊοΈ Created RIR", rir.name) diff --git a/startup_scripts/260_asns.py b/startup_scripts/260_asns.py index 893f3ba91..93776d247 100644 --- a/startup_scripts/260_asns.py +++ b/startup_scripts/260_asns.py @@ -1,7 +1,7 @@ import sys from ipam.models import ASN, RIR -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params from tenancy.models import Tenant asns = load_yaml("/opt/netbox/initializers/asns.yml") @@ -9,8 +9,8 @@ if asns is None: sys.exit() +match_params = ["asn", "rir"] required_assocs = {"rir": (RIR, "name")} - optional_assocs = {"tenant": (Tenant, "name")} for params in asns: @@ -27,7 +27,8 @@ params[assoc] = model.objects.get(**query) - asn, created = ASN.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + asn, created = ASN.objects.get_or_create(**matching_params, defaults=defaults) if created: print(f"πŸ”‘ Created ASN {asn.asn}") diff --git a/startup_scripts/270_aggregates.py b/startup_scripts/270_aggregates.py index 1cc7b5e86..d37c4cc7d 100644 --- a/startup_scripts/270_aggregates.py +++ b/startup_scripts/270_aggregates.py @@ -2,7 +2,12 @@ from ipam.models import RIR, Aggregate from netaddr import IPNetwork -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant aggregates = load_yaml("/opt/netbox/initializers/aggregates.yml") @@ -10,8 +15,8 @@ if aggregates is None: sys.exit() +match_params = ["prefix", "rir"] required_assocs = {"rir": (RIR, "name")} - optional_assocs = { "tenant": (Tenant, "name"), } @@ -34,7 +39,8 @@ params[assoc] = model.objects.get(**query) - aggregate, created = Aggregate.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + aggregate, created = Aggregate.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ—žοΈ Created Aggregate", aggregate.prefix) diff --git a/startup_scripts/280_prefix_vlan_roles.py b/startup_scripts/280_prefix_vlan_roles.py index ec359fbc0..c17feb853 100644 --- a/startup_scripts/280_prefix_vlan_roles.py +++ b/startup_scripts/280_prefix_vlan_roles.py @@ -1,7 +1,7 @@ import sys from ipam.models import Role -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params roles = load_yaml("/opt/netbox/initializers/prefix_vlan_roles.yml") @@ -9,7 +9,8 @@ sys.exit() for params in roles: - role, created = Role.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + role, created = Role.objects.get_or_create(**matching_params, defaults=defaults) if created: print("⛹️‍ Created Prefix/VLAN Role", role.name) diff --git a/startup_scripts/290_cluster_types.py b/startup_scripts/290_cluster_types.py index 9f361b1cf..642bc7a5c 100644 --- a/startup_scripts/290_cluster_types.py +++ b/startup_scripts/290_cluster_types.py @@ -1,6 +1,6 @@ import sys -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params from virtualization.models import ClusterType cluster_types = load_yaml("/opt/netbox/initializers/cluster_types.yml") @@ -9,7 +9,8 @@ sys.exit() for params in cluster_types: - cluster_type, created = ClusterType.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + cluster_type, created = ClusterType.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🧰 Created Cluster Type", cluster_type.name) diff --git a/startup_scripts/300_cluster_groups.py b/startup_scripts/300_cluster_groups.py index fedd292ec..a8e357337 100644 --- a/startup_scripts/300_cluster_groups.py +++ b/startup_scripts/300_cluster_groups.py @@ -1,6 +1,6 @@ import sys -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params from virtualization.models import ClusterGroup cluster_groups = load_yaml("/opt/netbox/initializers/cluster_groups.yml") @@ -9,7 +9,10 @@ sys.exit() for params in cluster_groups: - cluster_group, created = ClusterGroup.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + cluster_group, created = ClusterGroup.objects.get_or_create( + **matching_params, defaults=defaults + ) if created: print("πŸ—„οΈ Created Cluster Group", cluster_group.name) diff --git a/startup_scripts/310_clusters.py b/startup_scripts/310_clusters.py index e4923518a..e36a5f9e7 100644 --- a/startup_scripts/310_clusters.py +++ b/startup_scripts/310_clusters.py @@ -1,7 +1,12 @@ import sys from dcim.models import Site -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant from virtualization.models import Cluster, ClusterGroup, ClusterType @@ -10,8 +15,8 @@ if clusters is None: sys.exit() +match_params = ["name", "type"] required_assocs = {"type": (ClusterType, "name")} - optional_assocs = { "site": (Site, "name"), "group": (ClusterGroup, "name"), @@ -34,7 +39,8 @@ params[assoc] = model.objects.get(**query) - cluster, created = Cluster.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + cluster, created = Cluster.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ—„οΈ Created cluster", cluster.name) diff --git a/startup_scripts/320_vlan_groups.py b/startup_scripts/320_vlan_groups.py index 7b5fe6db4..a81e695f3 100644 --- a/startup_scripts/320_vlan_groups.py +++ b/startup_scripts/320_vlan_groups.py @@ -2,7 +2,12 @@ from django.contrib.contenttypes.models import ContentType from ipam.models import VLANGroup -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) vlan_groups = load_yaml("/opt/netbox/initializers/vlan_groups.yml") @@ -32,7 +37,9 @@ ) continue params["scope_id"] = ct.model_class().objects.get(**query).id - vlan_group, created = VLANGroup.objects.get_or_create(**params) + + matching_params, defaults = split_params(params) + vlan_group, created = VLANGroup.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🏘️ Created VLAN Group", vlan_group.name) diff --git a/startup_scripts/330_vlans.py b/startup_scripts/330_vlans.py index 8a6b0c84e..e378881bc 100644 --- a/startup_scripts/330_vlans.py +++ b/startup_scripts/330_vlans.py @@ -2,7 +2,12 @@ from dcim.models import Site from ipam.models import VLAN, Role, VLANGroup -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant, TenantGroup vlans = load_yaml("/opt/netbox/initializers/vlans.yml") @@ -10,6 +15,7 @@ if vlans is None: sys.exit() +match_params = ["name", "vid"] optional_assocs = { "site": (Site, "name"), "tenant": (Tenant, "name"), @@ -28,7 +34,8 @@ params[assoc] = model.objects.get(**query) - vlan, created = VLAN.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + vlan, created = VLAN.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🏠 Created VLAN", vlan.name) diff --git a/startup_scripts/340_virtual_machines.py b/startup_scripts/340_virtual_machines.py index 94d4fefab..51d3bc6fb 100644 --- a/startup_scripts/340_virtual_machines.py +++ b/startup_scripts/340_virtual_machines.py @@ -1,7 +1,12 @@ import sys from dcim.models import DeviceRole, Platform -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant from virtualization.models import Cluster, VirtualMachine @@ -10,8 +15,8 @@ if virtual_machines is None: sys.exit() +match_params = ["cluster", "name"] required_assocs = {"cluster": (Cluster, "name")} - optional_assocs = { "tenant": (Tenant, "name"), "platform": (Platform, "name"), @@ -38,7 +43,10 @@ params[assoc] = model.objects.get(**query) - virtual_machine, created = VirtualMachine.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + virtual_machine, created = VirtualMachine.objects.get_or_create( + **matching_params, defaults=defaults + ) if created: print("πŸ–₯️ Created virtual machine", virtual_machine.name) diff --git a/startup_scripts/350_virtualization_interfaces.py b/startup_scripts/350_virtualization_interfaces.py index 0ae5c5c8a..c6bbfa050 100644 --- a/startup_scripts/350_virtualization_interfaces.py +++ b/startup_scripts/350_virtualization_interfaces.py @@ -1,6 +1,11 @@ import sys -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from virtualization.models import VirtualMachine, VMInterface interfaces = load_yaml("/opt/netbox/initializers/virtualization_interfaces.yml") @@ -8,6 +13,7 @@ if interfaces is None: sys.exit() +match_params = ["name", "virtual_machine"] required_assocs = {"virtual_machine": (VirtualMachine, "name")} for params in interfaces: @@ -19,7 +25,8 @@ params[assoc] = model.objects.get(**query) - interface, created = VMInterface.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + interface, created = VMInterface.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🧷 Created interface", interface.name, interface.virtual_machine.name) diff --git a/startup_scripts/360_prefixes.py b/startup_scripts/360_prefixes.py index c4cb93659..ffb774bfe 100644 --- a/startup_scripts/360_prefixes.py +++ b/startup_scripts/360_prefixes.py @@ -3,7 +3,12 @@ from dcim.models import Site from ipam.models import VLAN, VRF, Prefix, Role from netaddr import IPNetwork -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant, TenantGroup prefixes = load_yaml("/opt/netbox/initializers/prefixes.yml") @@ -11,6 +16,7 @@ if prefixes is None: sys.exit() +match_params = ["prefix", "site", "vrf", "vlan"] optional_assocs = { "site": (Site, "name"), "tenant": (Tenant, "name"), @@ -31,7 +37,8 @@ query = {field: params.pop(assoc)} params[assoc] = model.objects.get(**query) - prefix, created = Prefix.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + prefix, created = Prefix.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ“Œ Created Prefix", prefix.prefix) diff --git a/startup_scripts/370_ip_addresses.py b/startup_scripts/370_ip_addresses.py index afe761214..b35cb49e2 100644 --- a/startup_scripts/370_ip_addresses.py +++ b/startup_scripts/370_ip_addresses.py @@ -5,7 +5,12 @@ from django.db.models import Q from ipam.models import VRF, IPAddress from netaddr import IPNetwork -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant from virtualization.models import VirtualMachine, VMInterface @@ -14,6 +19,7 @@ if ip_addresses is None: sys.exit() +match_params = ["address", "vrf"] optional_assocs = { "tenant": (Tenant, "name"), "vrf": (VRF, "name"), @@ -55,7 +61,8 @@ params[assoc] = model.objects.get(**query) - ip_address, created = IPAddress.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + ip_address, created = IPAddress.objects.get_or_create(**matching_params, defaults=defaults) if created: print("🧬 Created IP Address", ip_address.address) diff --git a/startup_scripts/400_services.py b/startup_scripts/400_services.py index a28eb09f7..a92d82b1d 100644 --- a/startup_scripts/400_services.py +++ b/startup_scripts/400_services.py @@ -2,7 +2,7 @@ from dcim.models import Device from ipam.models import Service -from startup_script_utils import load_yaml +from startup_script_utils import load_yaml, split_params from virtualization.models import VirtualMachine services = load_yaml("/opt/netbox/initializers/services.yml") @@ -10,6 +10,7 @@ if services is None: sys.exit() +match_params = ["name", "device", "virtual_machine"] optional_assocs = { "device": (Device, "name"), "virtual_machine": (VirtualMachine, "name"), @@ -24,6 +25,7 @@ params[assoc] = model.objects.get(**query) + matching_params, defaults = split_params(params, match_params) service, created = Service.objects.get_or_create(**params) if created: diff --git a/startup_scripts/420_providers.py b/startup_scripts/420_providers.py index 1c652eb9e..e68e1f369 100644 --- a/startup_scripts/420_providers.py +++ b/startup_scripts/420_providers.py @@ -1,7 +1,12 @@ import sys from circuits.models import Provider -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) providers = load_yaml("/opt/netbox/initializers/providers.yml") @@ -11,7 +16,8 @@ for params in providers: custom_field_data = pop_custom_fields(params) - provider, created = Provider.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + provider, created = Provider.objects.get_or_create(**matching_params, defaults=defaults) if created: print("πŸ“‘ Created provider", provider.name) diff --git a/startup_scripts/440_circuit_types.py b/startup_scripts/440_circuit_types.py index fa49681c3..6d3f7fa3e 100644 --- a/startup_scripts/440_circuit_types.py +++ b/startup_scripts/440_circuit_types.py @@ -1,7 +1,12 @@ import sys from circuits.models import CircuitType -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) circuit_types = load_yaml("/opt/netbox/initializers/circuit_types.yml") @@ -11,7 +16,8 @@ for params in circuit_types: custom_field_data = pop_custom_fields(params) - circuit_type, created = CircuitType.objects.get_or_create(**params) + matching_params, defaults = split_params(params) + circuit_type, created = CircuitType.objects.get_or_create(**matching_params, defaults=defaults) if created: print("⚑ Created Circuit Type", circuit_type.name) diff --git a/startup_scripts/450_circuits.py b/startup_scripts/450_circuits.py index c1822039e..68f159b5b 100644 --- a/startup_scripts/450_circuits.py +++ b/startup_scripts/450_circuits.py @@ -1,7 +1,12 @@ import sys from circuits.models import Circuit, CircuitType, Provider -from startup_script_utils import load_yaml, pop_custom_fields, set_custom_fields_values +from startup_script_utils import ( + load_yaml, + pop_custom_fields, + set_custom_fields_values, + split_params, +) from tenancy.models import Tenant circuits = load_yaml("/opt/netbox/initializers/circuits.yml") @@ -9,8 +14,8 @@ if circuits is None: sys.exit() +match_params = ["cid", "provider", "type"] required_assocs = {"provider": (Provider, "name"), "type": (CircuitType, "name")} - optional_assocs = {"tenant": (Tenant, "name")} for params in circuits: @@ -29,7 +34,8 @@ params[assoc] = model.objects.get(**query) - circuit, created = Circuit.objects.get_or_create(**params) + matching_params, defaults = split_params(params, match_params) + circuit, created = Circuit.objects.get_or_create(**matching_params, defaults=defaults) if created: print("⚑ Created Circuit", circuit.cid) diff --git a/startup_scripts/startup_script_utils/__init__.py b/startup_scripts/startup_script_utils/__init__.py index 290b87b4f..4c25c6bee 100644 --- a/startup_scripts/startup_script_utils/__init__.py +++ b/startup_scripts/startup_script_utils/__init__.py @@ -1,2 +1,3 @@ from .custom_fields import pop_custom_fields, set_custom_fields_values from .load_yaml import load_yaml +from .utils import split_params diff --git a/startup_scripts/startup_script_utils/utils.py b/startup_scripts/startup_script_utils/utils.py new file mode 100644 index 000000000..e31f796f5 --- /dev/null +++ b/startup_scripts/startup_script_utils/utils.py @@ -0,0 +1,15 @@ +from typing import Tuple + + +def split_params(params: dict, unique_params: list = None) -> Tuple[dict, dict]: + """Split params dict into dict with matching params and a dict with default values""" + + if unique_params is None: + unique_params = ["name", "slug"] + + matching_params = {} + for unique_param in unique_params: + param = params.pop(unique_param, None) + if param: + matching_params[unique_param] = param + return matching_params, params From a63af05bec348a234e41f50e362aa6c0b1e646c7 Mon Sep 17 00:00:00 2001 From: kr3ator <48438188+kr3ator@users.noreply.github.com> Date: Fri, 8 Apr 2022 14:57:37 +0200 Subject: [PATCH 5/9] Update initializers/users.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Christian MΓ€der --- initializers/users.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/initializers/users.yml b/initializers/users.yml index 2dca92d6f..d6484c209 100644 --- a/initializers/users.yml +++ b/initializers/users.yml @@ -4,7 +4,7 @@ # password: reader # writer: # password: writer -# api_token: # leave empty to not generate any token +# api_token: "" # a token is generated automatically unless the value is explicity set to empty # jdoe: # first_name: John # last_name: Doe From 19280c2bb0e79be01daabc2cb1f56831849aa365 Mon Sep 17 00:00:00 2001 From: Robin Beismann Date: Fri, 8 Apr 2022 15:36:49 +0200 Subject: [PATCH 6/9] Fixed default value to reflect upstream --- configuration/configuration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configuration/configuration.py b/configuration/configuration.py index 7bfa6a6c1..78954f935 100644 --- a/configuration/configuration.py +++ b/configuration/configuration.py @@ -123,7 +123,7 @@ def _read_secret(secret_name, default = None): # Cross-Site-Request-Forgery-Attack settings. If Netbox is sitting behind a reverse proxy, you might need to set the CSRF_TRUSTED_ORIGINS flag. # Django 4.0 requires to specify the URL Scheme in this setting. An example environment variable could be specified like: # CSRF_TRUSTED_ORIGINS=https://demo.netbox.dev http://demo.netbox.dev -CSRF_TRUSTED_ORIGINS = list(filter(None, environ.get('CSRF_TRUSTED_ORIGINS', 'https://localhost').split(' '))) +CSRF_TRUSTED_ORIGINS = list(filter(None, environ.get('CSRF_TRUSTED_ORIGINS', '').split(' '))) # Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal # sensitive information about your installation. Only enable debugging while performing testing. Never enable debugging From dd8dce1b497c8eb42f286babe91f2945c0ec7a77 Mon Sep 17 00:00:00 2001 From: kr3ator <48438188+kr3ator@users.noreply.github.com> Date: Fri, 8 Apr 2022 10:36:16 +0200 Subject: [PATCH 7/9] feat: Add support for DeviceType components --- initializers/device_types.yml | 32 ++++++++++ startup_scripts/190_device_types.py | 97 +++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/initializers/device_types.yml b/initializers/device_types.yml index 88798b5b2..899a71856 100644 --- a/initializers/device_types.yml +++ b/initializers/device_types.yml @@ -21,3 +21,35 @@ # slug: other # custom_field_data: # text_field: Description +# interfaces: +# - name: eth0 +# type: 1000base-t +# mgmt_only: True +# - name: eth1 +# type: 1000base-t +# console_server_ports: +# - name_template: ttyS[1-48] +# type: rj-45 +# power_ports: +# - name_template: psu[0,1] +# type: iec-60320-c14 +# maximum_draw: 35 +# allocated_draw: 35 +# front_ports: +# - name_template: front[1,2] +# type: 8p8c +# rear_port_template: rear[0,1] +# rear_port_position_template: "[1,2]" +# rear_ports: +# - name_template: rear[0,1] +# type: 8p8c +# positions_template: "[3,2]" +# device_bays: +# - name_template: bay[0-9] +# label_template: test[0-5,9,6-8] +# description: Test description +# power_outlets: +# - name_template: outlet[0,1] +# type: iec-60320-c5 +# power_port: psu0 +# feed_leg: B diff --git a/startup_scripts/190_device_types.py b/startup_scripts/190_device_types.py index 98d7f5b4b..d1548058c 100644 --- a/startup_scripts/190_device_types.py +++ b/startup_scripts/190_device_types.py @@ -1,6 +1,17 @@ import sys +from typing import List from dcim.models import DeviceType, Manufacturer, Region +from dcim.models.device_component_templates import ( + ConsolePortTemplate, + ConsoleServerPortTemplate, + DeviceBayTemplate, + FrontPortTemplate, + InterfaceTemplate, + PowerOutletTemplate, + PowerPortTemplate, + RearPortTemplate, +) from startup_script_utils import ( load_yaml, pop_custom_fields, @@ -8,6 +19,53 @@ split_params, ) from tenancy.models import Tenant +from utilities.forms.utils import expand_alphanumeric_pattern + + +def expand_templates(params: List[dict], device_type: DeviceType) -> List[dict]: + templateable_fields = ["name", "label", "positions", "rear_port", "rear_port_position"] + + expanded = [] + for param in params: + param["device_type"] = device_type + expanded_fields = {} + has_plain_fields = False + + for field in templateable_fields: + template_value = param.pop(f"{field}_template", None) + + if field in param: + has_plain_fields = True + expanded.append(param) + elif template_value: + expanded_fields[field] = list(expand_alphanumeric_pattern(template_value)) + + if expanded_fields and has_plain_fields: + raise ValueError(f"Mix of plain and template keys provided for {templateable_fields}") + + if not expanded_fields: + continue + + elements = list(expanded_fields.values()) + master_len = len(elements[0]) + if not all([len(elem) == master_len for elem in elements]): + raise ValueError( + f"Number of elements in template fields " + f"{list(expanded_fields.keys())} must be equal" + ) + + for idx in range(master_len): + tmp = param.copy() + for field, value in expanded_fields.items(): + if field in nested_assocs: + model, match_key = nested_assocs[field] + query = {match_key: value[idx], "device_type": device_type} + tmp[field] = model.objects.get(**query) + else: + tmp[field] = value[idx] + expanded.append(tmp) + return expanded + device_types = load_yaml("/opt/netbox/initializers/device_types.yml") @@ -17,9 +75,22 @@ match_params = ["manufacturer", "model", "slug"] required_assocs = {"manufacturer": (Manufacturer, "name")} optional_assocs = {"region": (Region, "name"), "tenant": (Tenant, "name")} +nested_assocs = {"rear_port": (RearPortTemplate, "name"), "power_port": (PowerPortTemplate, "name")} + +supported_components = { + "interfaces": (InterfaceTemplate, ["name"]), + "console_ports": (ConsolePortTemplate, ["name"]), + "console_server_ports": (ConsoleServerPortTemplate, ["name"]), + "power_ports": (PowerPortTemplate, ["name"]), + "power_outlets": (PowerOutletTemplate, ["name"]), + "rear_ports": (RearPortTemplate, ["name"]), + "front_ports": (FrontPortTemplate, ["name"]), + "device_bays": (DeviceBayTemplate, ["name"]), +} for params in device_types: custom_field_data = pop_custom_fields(params) + components = [(v[0], v[1], params.pop(k, [])) for k, v in supported_components.items()] for assoc, details in required_assocs.items(): model, field = details @@ -41,3 +112,29 @@ print("πŸ”‘ Created device type", device_type.manufacturer, device_type.model) set_custom_fields_values(device_type, custom_field_data) + + for component in components: + c_model, c_match_params, c_params = component + c_match_params.append("device_type") + + if not c_params: + continue + + expanded_c_params = expand_templates(c_params, device_type) + + for n_assoc, n_details in nested_assocs.items(): + n_model, n_field = n_details + for c_param in expanded_c_params: + if n_assoc in c_param: + n_query = {n_field: c_param[n_assoc], "device_type": device_type} + c_param[n_assoc] = n_model.objects.get(**n_query) + + for new_param in expanded_c_params: + new_matching_params, new_defaults = split_params(new_param, c_match_params) + new_obj, new_obj_created = c_model.objects.get_or_create( + **new_matching_params, defaults=new_defaults + ) + if new_obj_created: + print( + f"🧷 Created {c_model._meta} {new_obj} component for device type {device_type}" + ) From 12753dd7d44fa7a0652f953fa2344f2dfc716aa0 Mon Sep 17 00:00:00 2001 From: kr3ator <48438188+kr3ator@users.noreply.github.com> Date: Fri, 8 Apr 2022 15:10:35 +0200 Subject: [PATCH 8/9] Document field name precedence --- initializers/device_types.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/initializers/device_types.yml b/initializers/device_types.yml index 899a71856..ffb45fe03 100644 --- a/initializers/device_types.yml +++ b/initializers/device_types.yml @@ -31,7 +31,8 @@ # - name_template: ttyS[1-48] # type: rj-45 # power_ports: -# - name_template: psu[0,1] +# - name: psu0 # both non-template and template field specified; non-template field takes precedence +# name_template: psu[0,1] # type: iec-60320-c14 # maximum_draw: 35 # allocated_draw: 35 From 0e7afe466dc1f138f7ca8934f070b9760f518f49 Mon Sep 17 00:00:00 2001 From: kr3ator <48438188+kr3ator@users.noreply.github.com> Date: Fri, 8 Apr 2022 18:25:52 +0200 Subject: [PATCH 9/9] feat: Add support for bridge, lag, parent --- initializers/dcim_interfaces.yml | 9 ++++++ startup_scripts/210_dcim_interfaces.py | 38 +++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/initializers/dcim_interfaces.yml b/initializers/dcim_interfaces.yml index 40305306b..4db416f27 100644 --- a/initializers/dcim_interfaces.yml +++ b/initializers/dcim_interfaces.yml @@ -8,6 +8,15 @@ ## ## Examples: +# - device: server01 +# name: ath0 +# type: 1000base-t +# lag: ae0 +# bridge: br0 +# - device: server01 +# name: ath1 +# type: 1000base-t +# parent: ath0 # - device: server01 # enabled: true # type: virtual diff --git a/startup_scripts/210_dcim_interfaces.py b/startup_scripts/210_dcim_interfaces.py index b217f3b04..6e57e73ce 100644 --- a/startup_scripts/210_dcim_interfaces.py +++ b/startup_scripts/210_dcim_interfaces.py @@ -15,10 +15,17 @@ match_params = ["device", "name"] required_assocs = {"device": (Device, "name")} +related_assocs = { + "bridge": (Interface, "name"), + "lag": (Interface, "name"), + "parent": (Interface, "name"), +} for params in interfaces: custom_field_data = pop_custom_fields(params) + related_interfaces = {k: params.pop(k, None) for k in related_assocs} + for assoc, details in required_assocs.items(): model, field = details query = {field: params.pop(assoc)} @@ -29,6 +36,35 @@ interface, created = Interface.objects.get_or_create(**matching_params, defaults=defaults) if created: - print("🧷 Created interface", interface.name, interface.device.name) + print(f"🧷 Created interface {interface} on {interface.device}") set_custom_fields_values(interface, custom_field_data) + + for related_field, related_value in related_interfaces.items(): + if not related_value: + continue + + r_model, r_field = related_assocs[related_field] + + if related_field == "parent" and not interface.parent_id: + query = {r_field: related_value, "device": interface.device} + try: + related_obj = r_model.objects.get(**query) + except Interface.DoesNotExist: + print(f"⚠️ Could not find parent interface with: {query} for interface {interface}") + raise + + interface.parent_id = related_obj.id + interface.save() + print( + f"🧷 Attached interface {interface} on {interface.device} " + f"to parent {related_obj}" + ) + else: + query = {r_field: related_value, "device": interface.device, "type": related_field} + related_obj, rel_obj_created = r_model.objects.get_or_create(**query) + + if rel_obj_created: + setattr(interface, f"{related_field}_id", related_obj.id) + interface.save() + print(f"🧷 Created {related_field} interface {interface} on {interface.device}")