ipfabric_netbox 4.3.2b1__tar.gz → 4.3.2b3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/PKG-INFO +2 -1
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/__init__.py +1 -1
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/data/transform_map.json +11 -11
- ipfabric_netbox-4.3.2b3/ipfabric_netbox/exceptions.py +46 -0
- ipfabric_netbox-4.3.2b3/ipfabric_netbox/migrations/0021_update_transform_maps.py +133 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/models.py +36 -17
- ipfabric_netbox-4.3.2b3/ipfabric_netbox/signals.py +41 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/inc/diff.html +7 -7
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/tests/test_models.py +24 -19
- ipfabric_netbox-4.3.2b3/ipfabric_netbox/utilities/ipfutils.py +984 -0
- ipfabric_netbox-4.3.2b3/ipfabric_netbox/utilities/transform_map.py +233 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/pyproject.toml +2 -1
- ipfabric_netbox-4.3.2b1/ipfabric_netbox/exceptions.py +0 -81
- ipfabric_netbox-4.3.2b1/ipfabric_netbox/signals.py +0 -53
- ipfabric_netbox-4.3.2b1/ipfabric_netbox/utilities/ipfutils.py +0 -863
- ipfabric_netbox-4.3.2b1/ipfabric_netbox/utilities/nbutils.py +0 -79
- ipfabric_netbox-4.3.2b1/ipfabric_netbox/utilities/transform_map.py +0 -60
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/README.md +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/api/__init__.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/api/serializers.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/api/urls.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/api/views.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/choices.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/filtersets.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/forms.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/graphql/__init__.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/graphql/enums.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/graphql/filters.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/graphql/schema.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/graphql/types.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/jobs.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0001_initial.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0001_initial_squashed_0013_switch_to_branching_plugin.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0002_ipfabricsnapshot_status.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0003_ipfabricsource_type_and_more.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0004_ipfabricsync_auto_merge.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0005_alter_ipfabricrelationshipfield_source_model_and_more.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0006_alter_ipfabrictransformmap_target_model.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0007_prepare_custom_fields.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0008_prepare_transform_maps.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0009_transformmap_changes_for_netbox_v4_2.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0010_remove_uuid_from_get_or_create.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0011_update_part_number_DCIM_inventory_item_template.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0012_remove_status_field.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0013_switch_to_branching_plugin.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0014_ipfabrictransformmapgroup_ipfabrictransformmap_group.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0015_ipfabricingestionissue.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0016_tags_and_changelog_for_snapshots.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0017_ipfabricsync_update_custom_fields.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0018_remove_type_field.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0019_alter_ipfabrictransformmap_options_and_more.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/0020_clean_scheduled_jobs.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/migrations/__init__.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/navigation.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/tables.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/template_content.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/inc/clone_form.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/inc/json.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/inc/logs_pending.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/inc/merge_form.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/inc/site_topology_button.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/inc/site_topology_modal.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/inc/snapshotdata.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_field_map.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/inc/transform_map_relationship_map.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/ipfabric_table.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricingestion.html +2 -2
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsnapshot.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsource.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap_list.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap_restore.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmapgroup.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_all.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_progress.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_statistics.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_status.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/partials/job_logs.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/partials/object_tabs.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/ipfabric_netbox/partials/sync_last_ingestion.html +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templates/static/ipfabric_netbox/css/rack.css +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templatetags/__init__.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/templatetags/ipfabric_netbox_helpers.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/tests/__init__.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/tests/api/__init__.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/tests/api/test_api.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/tests/test_forms.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/tests/test_views.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/urls.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/utilities/__init__.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/utilities/logging.py +0 -0
- {ipfabric_netbox-4.3.2b1 → ipfabric_netbox-4.3.2b3}/ipfabric_netbox/views.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ipfabric_netbox
|
|
3
|
-
Version: 4.3.
|
|
3
|
+
Version: 4.3.2b3
|
|
4
4
|
Summary: NetBox plugin to sync IP Fabric data into NetBox
|
|
5
5
|
License: MIT
|
|
6
6
|
Keywords: netbox,ipfabric,plugin,sync
|
|
@@ -19,6 +19,7 @@ Provides-Extra: ipfabric-6-10
|
|
|
19
19
|
Provides-Extra: ipfabric-7-0
|
|
20
20
|
Provides-Extra: ipfabric-7-2
|
|
21
21
|
Provides-Extra: ipfabric-7-3
|
|
22
|
+
Requires-Dist: httpx (>0.26,<0.29)
|
|
22
23
|
Requires-Dist: ipfabric (>=6.10.0,<6.11.0) ; extra == "ipfabric_6_10" and extra != "ipfabric_7_0" and extra != "ipfabric_7_2" and extra != "ipfabric_7_3"
|
|
23
24
|
Requires-Dist: ipfabric (>=6.6.4) ; extra != "ipfabric_6_10" and extra != "ipfabric_7_0" and extra != "ipfabric_7_2" and extra != "ipfabric_7_3"
|
|
24
25
|
Requires-Dist: ipfabric (>=7.0.0,<7.1.0) ; extra != "ipfabric_6_10" and extra == "ipfabric_7_0" and extra != "ipfabric_7_2" and extra != "ipfabric_7_3"
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
"model": "vrf"
|
|
42
42
|
},
|
|
43
43
|
"target_field": "vrf",
|
|
44
|
-
"coalesce":
|
|
45
|
-
"template": "{% if object.vrf is defined and object.vrf | string not in [\"\", \"system\", \"0\"] %}{{ ipam.VRF.objects.filter(name=object.vrf).first().pk }}{% else %}None{% endif %}"
|
|
44
|
+
"coalesce": true,
|
|
45
|
+
"template": "{% if object.vrf is defined and object.vrf | string not in [\"\", \"system\", \"0\", \"global\"] %}{{ ipam.VRF.objects.filter(name=object.vrf).first().pk }}{% else %}None{% endif %}"
|
|
46
46
|
}
|
|
47
47
|
]
|
|
48
48
|
},
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
"source_field": "hostname",
|
|
170
170
|
"target_field": "vc_position",
|
|
171
171
|
"coalesce": false,
|
|
172
|
-
"template": "{% if object.virtual_chassis
|
|
172
|
+
"template": "{% if object.virtual_chassis %}{{ object.virtual_chassis.member }}{% else %}None{% endif %}"
|
|
173
173
|
}
|
|
174
174
|
],
|
|
175
175
|
"relationship_maps": [
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
},
|
|
181
181
|
"target_field": "virtual_chassis",
|
|
182
182
|
"coalesce": false,
|
|
183
|
-
"template": "{% if object.virtual_chassis
|
|
183
|
+
"template": "{% if object.virtual_chassis %}{{ dcim.VirtualChassis.objects.filter(name=object.virtual_chassis.master).first().pk }}{% endif %}"
|
|
184
184
|
},
|
|
185
185
|
{
|
|
186
186
|
"source_model": {
|
|
@@ -339,7 +339,7 @@
|
|
|
339
339
|
"source_field": "primaryIp",
|
|
340
340
|
"target_field": "mgmt_only",
|
|
341
341
|
"coalesce": false,
|
|
342
|
-
"template": "{% if object.primaryIp == object.loginIp %}True{% else %}False{% endif %}"
|
|
342
|
+
"template": "{% if object.primaryIp and object.primaryIp == object.loginIp %}True{% else %}False{% endif %}"
|
|
343
343
|
},
|
|
344
344
|
{
|
|
345
345
|
"source_field": "speedValue",
|
|
@@ -386,10 +386,10 @@
|
|
|
386
386
|
"template": "{% if object.mac %}{{ object.mac | mac_to_format(frmt=\"MAC_COLON_TWO\") | upper }}{% else %}{{ \"00:00:00:00:00:01\" | mac_to_format(frmt=\"MAC_COLON_TWO\") | upper }}{% endif %}"
|
|
387
387
|
},
|
|
388
388
|
{
|
|
389
|
-
"source_field": "
|
|
389
|
+
"source_field": "sn",
|
|
390
390
|
"target_field": "assigned_object_id",
|
|
391
391
|
"coalesce": true,
|
|
392
|
-
"template": ""
|
|
392
|
+
"template": "{% if object.nameOriginal %}{{ dcim.Interface.objects.filter(device__serial=object.sn, name=object.nameOriginal).first().pk }}{% else %}{{ dcim.Interface.objects.filter(device__serial=object.sn, name=object.intName).first().pk }}{% endif %}"
|
|
393
393
|
}
|
|
394
394
|
],
|
|
395
395
|
"relationship_maps": [
|
|
@@ -432,7 +432,7 @@
|
|
|
432
432
|
{
|
|
433
433
|
"source_field": "name",
|
|
434
434
|
"target_field": "name",
|
|
435
|
-
"coalesce":
|
|
435
|
+
"coalesce": true,
|
|
436
436
|
"template": "{% if object.name is not none %}{{ object.name | string | truncate(64, True) }}{% elif object.dscr is not none %}{{ object.dscr | string | truncate(64, True) }}{% else %}Default Name{% endif %}"
|
|
437
437
|
}
|
|
438
438
|
],
|
|
@@ -443,7 +443,7 @@
|
|
|
443
443
|
"model": "device"
|
|
444
444
|
},
|
|
445
445
|
"target_field": "device",
|
|
446
|
-
"coalesce":
|
|
446
|
+
"coalesce": true,
|
|
447
447
|
"template": "{{ dcim.Device.objects.get(serial=object.deviceSn).pk }}"
|
|
448
448
|
},
|
|
449
449
|
{
|
|
@@ -474,7 +474,7 @@
|
|
|
474
474
|
"source_field": "vlanName",
|
|
475
475
|
"target_field": "name",
|
|
476
476
|
"coalesce": false,
|
|
477
|
-
"template": "{% if object.vlanName is defined and object.vlanName | lower
|
|
477
|
+
"template": "{% if object.vlanName is defined and object.vlanName | lower not in [\"none\", \"\"] %}{{ object.vlanName | string | truncate(64, True) }}{% else %}\"\"{% endif %}"
|
|
478
478
|
},
|
|
479
479
|
{
|
|
480
480
|
"source_field": "dscr",
|
|
@@ -563,7 +563,7 @@
|
|
|
563
563
|
},
|
|
564
564
|
"target_field": "vrf",
|
|
565
565
|
"coalesce": true,
|
|
566
|
-
"template": "{% if object.vrf is defined and object.vrf | string not in [\"\", \"system\", \"0\"] %}{{ ipam.VRF.objects.filter(name=object.vrf).first().pk }}{% else %}None{% endif %}"
|
|
566
|
+
"template": "{% if object.vrf is defined and object.vrf | string not in [\"\", \"system\", \"0\", \"global\"] %}{{ ipam.VRF.objects.filter(name=object.vrf).first().pk }}{% else %}None{% endif %}"
|
|
567
567
|
},
|
|
568
568
|
{
|
|
569
569
|
"source_model": {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from core.exceptions import SyncError
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class IngestionIssue(Exception):
|
|
5
|
+
"""
|
|
6
|
+
This exception is used to indicate an issue during the ingestion process.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
# Store created issue object ID if it exists for this exception
|
|
10
|
+
issue_id = None
|
|
11
|
+
model: str = ""
|
|
12
|
+
defaults: dict[str, str] = {}
|
|
13
|
+
coalesce_fields: dict[str, str] = {}
|
|
14
|
+
|
|
15
|
+
def __init__(self, model: str, data: dict, context: dict = None, issue_id=None):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.model = model
|
|
18
|
+
self.data = data
|
|
19
|
+
context = context or {}
|
|
20
|
+
self.defaults = context.pop("defaults", {})
|
|
21
|
+
self.coalesce_fields = context
|
|
22
|
+
self.issue_id = issue_id
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SearchError(IngestionIssue, LookupError):
|
|
26
|
+
def __init__(self, message: str, *args, **kwargs):
|
|
27
|
+
super().__init__(*args, **kwargs)
|
|
28
|
+
self.message = message
|
|
29
|
+
|
|
30
|
+
def __str__(self):
|
|
31
|
+
return self.message
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SyncDataError(IngestionIssue, SyncError):
|
|
35
|
+
def __str__(self):
|
|
36
|
+
return f"Sync failed for {self.model}: coalesce_fields={self.coalesce_fields} defaults={self.defaults}."
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class IPAddressDuplicateError(IngestionIssue, SyncError):
|
|
40
|
+
def __str__(self):
|
|
41
|
+
return f"IP address {self.data.get('address')} already exists in {self.model} with coalesce_fields={self.coalesce_fields}."
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class IPAddressPrimaryRemovalError(IngestionIssue, SyncError):
|
|
45
|
+
def __str__(self):
|
|
46
|
+
return "Error removing primary IP from other device."
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from django.db import migrations
|
|
2
|
+
|
|
3
|
+
from ipfabric_netbox.utilities.transform_map import do_change
|
|
4
|
+
from ipfabric_netbox.utilities.transform_map import FieldRecord
|
|
5
|
+
from ipfabric_netbox.utilities.transform_map import RelationshipRecord
|
|
6
|
+
from ipfabric_netbox.utilities.transform_map import TransformMapRecord
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
CHANGES = (
|
|
10
|
+
TransformMapRecord(
|
|
11
|
+
source_model="ipaddress",
|
|
12
|
+
target_model="ipam.ipaddress",
|
|
13
|
+
relationships=(
|
|
14
|
+
RelationshipRecord(
|
|
15
|
+
source_model="ipam.vrf",
|
|
16
|
+
target_field="vrf",
|
|
17
|
+
coalesce=True,
|
|
18
|
+
old_template='{% if object.vrf is defined and object.vrf | string not in ["", "system", "0"] %}{{ ipam.VRF.objects.filter(name=object.vrf).first().pk }}{% else %}None{% endif %}',
|
|
19
|
+
new_template='{% if object.vrf is defined and object.vrf | string not in ["", "system", "0", "global"] %}{{ ipam.VRF.objects.filter(name=object.vrf).first().pk }}{% else %}None{% endif %}',
|
|
20
|
+
),
|
|
21
|
+
),
|
|
22
|
+
),
|
|
23
|
+
TransformMapRecord(
|
|
24
|
+
source_model="device",
|
|
25
|
+
target_model="dcim.device",
|
|
26
|
+
fields=(
|
|
27
|
+
FieldRecord(
|
|
28
|
+
source_field="hostname",
|
|
29
|
+
target_field="vc_position",
|
|
30
|
+
old_template="{% if object.virtual_chassis is defined %}{{ object.virtual_chassis.member }}{% else %}None{% endif %}",
|
|
31
|
+
new_template="{% if object.virtual_chassis %}{{ object.virtual_chassis.member }}{% else %}None{% endif %}",
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
relationships=(
|
|
35
|
+
RelationshipRecord(
|
|
36
|
+
source_model="dcim.virtualchassis",
|
|
37
|
+
target_field="virtual_chassis",
|
|
38
|
+
old_template="{% if object.virtual_chassis is defined %}{{ dcim.VirtualChassis.objects.filter(name=object.virtual_chassis.master).first().pk }}{% endif %}",
|
|
39
|
+
new_template="{% if object.virtual_chassis %}{{ dcim.VirtualChassis.objects.filter(name=object.virtual_chassis.master).first().pk }}{% endif %}",
|
|
40
|
+
),
|
|
41
|
+
),
|
|
42
|
+
),
|
|
43
|
+
TransformMapRecord(
|
|
44
|
+
source_model="interface",
|
|
45
|
+
target_model="dcim.interface",
|
|
46
|
+
fields=(
|
|
47
|
+
FieldRecord(
|
|
48
|
+
source_field="primaryIp",
|
|
49
|
+
target_field="mgmt_only",
|
|
50
|
+
old_template="{% if object.primaryIp == object.loginIp %}True{% else %}False{% endif %}",
|
|
51
|
+
new_template="{% if object.primaryIp and object.primaryIp == object.loginIp %}True{% else %}False{% endif %}",
|
|
52
|
+
),
|
|
53
|
+
),
|
|
54
|
+
),
|
|
55
|
+
TransformMapRecord(
|
|
56
|
+
source_model="interface",
|
|
57
|
+
target_model="dcim.macaddress",
|
|
58
|
+
fields=(
|
|
59
|
+
FieldRecord(
|
|
60
|
+
source_field="id",
|
|
61
|
+
new_source_field="sn",
|
|
62
|
+
target_field="assigned_object_id",
|
|
63
|
+
old_template="",
|
|
64
|
+
new_template="{% if object.nameOriginal %}{{ dcim.Interface.objects.filter(device__serial=object.sn, name=object.nameOriginal).first().pk }}{% else %}{{ dcim.Interface.objects.filter(device__serial=object.sn, name=object.intName).first().pk }}{% endif %}",
|
|
65
|
+
),
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
TransformMapRecord(
|
|
69
|
+
source_model="part_number",
|
|
70
|
+
target_model="dcim.inventoryitem",
|
|
71
|
+
fields=(
|
|
72
|
+
FieldRecord(
|
|
73
|
+
source_field="name",
|
|
74
|
+
target_field="name",
|
|
75
|
+
coalesce=True,
|
|
76
|
+
),
|
|
77
|
+
),
|
|
78
|
+
relationships=(
|
|
79
|
+
RelationshipRecord(
|
|
80
|
+
source_model="dcim.device",
|
|
81
|
+
target_field="device",
|
|
82
|
+
coalesce=True,
|
|
83
|
+
),
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
TransformMapRecord(
|
|
87
|
+
source_model="vlan",
|
|
88
|
+
target_model="ipam.vlan",
|
|
89
|
+
fields=(
|
|
90
|
+
FieldRecord(
|
|
91
|
+
source_field="vlanName",
|
|
92
|
+
target_field="name",
|
|
93
|
+
old_template='{% if object.vlanName is defined and object.vlanName | lower != "none" %}{{ object.vlanName | string | truncate(64, True) }}{% else %}""{% endif %}',
|
|
94
|
+
new_template='{% if object.vlanName is defined and object.vlanName | lower not in ["none", ""] %}{{ object.vlanName | string | truncate(64, True) }}{% else %}""{% endif %}',
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
),
|
|
98
|
+
TransformMapRecord(
|
|
99
|
+
source_model="prefix",
|
|
100
|
+
target_model="ipam.prefix",
|
|
101
|
+
relationships=(
|
|
102
|
+
RelationshipRecord(
|
|
103
|
+
source_model="ipam.vrf",
|
|
104
|
+
target_field="vrf",
|
|
105
|
+
old_template='{% if object.vrf is defined and object.vrf | string not in ["", "system", "0"] %}{{ ipam.VRF.objects.filter(name=object.vrf).first().pk }}{% else %}None{% endif %}',
|
|
106
|
+
new_template='{% if object.vrf is defined and object.vrf | string not in ["", "system", "0", "global"] %}{{ ipam.VRF.objects.filter(name=object.vrf).first().pk }}{% else %}None{% endif %}',
|
|
107
|
+
),
|
|
108
|
+
),
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def forward_change(apps, schema_editor):
|
|
114
|
+
"""Replace old template with updated version."""
|
|
115
|
+
do_change(apps, schema_editor, changes=CHANGES, forward=True)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def revert_change(apps, schema_editor):
|
|
119
|
+
"""Revert template back to the previous exact template."""
|
|
120
|
+
do_change(apps, schema_editor, changes=CHANGES, forward=False)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class Migration(migrations.Migration):
|
|
124
|
+
dependencies = [
|
|
125
|
+
("ipfabric_netbox", "0020_clean_scheduled_jobs"),
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
operations = [
|
|
129
|
+
migrations.RunPython(
|
|
130
|
+
forward_change,
|
|
131
|
+
revert_change,
|
|
132
|
+
),
|
|
133
|
+
]
|
|
@@ -2,6 +2,7 @@ import ast
|
|
|
2
2
|
import functools
|
|
3
3
|
import json
|
|
4
4
|
import logging
|
|
5
|
+
import re
|
|
5
6
|
import traceback
|
|
6
7
|
from copy import deepcopy
|
|
7
8
|
from uuid import uuid4
|
|
@@ -12,10 +13,8 @@ from core.exceptions import SyncError
|
|
|
12
13
|
from core.models import Job
|
|
13
14
|
from core.models import ObjectType
|
|
14
15
|
from core.signals import pre_sync
|
|
15
|
-
from dcim.models import
|
|
16
|
+
from dcim.models import MACAddress
|
|
16
17
|
from dcim.models import Site
|
|
17
|
-
from dcim.models import VirtualChassis
|
|
18
|
-
from dcim.signals import assign_virtualchassis_master
|
|
19
18
|
from django.apps import apps
|
|
20
19
|
from django.conf import settings
|
|
21
20
|
from django.contrib.contenttypes.models import ContentType
|
|
@@ -50,7 +49,7 @@ from .choices import IPFabricSourceTypeChoices
|
|
|
50
49
|
from .choices import IPFabricSyncStatusChoices
|
|
51
50
|
from .choices import IPFabricTransformMapSourceModelChoices
|
|
52
51
|
from .choices import required_transform_map_contenttypes
|
|
53
|
-
from .signals import
|
|
52
|
+
from .signals import assign_primary_mac_address
|
|
54
53
|
from .utilities.ipfutils import IPFabric
|
|
55
54
|
from .utilities.ipfutils import IPFabricSyncRunner
|
|
56
55
|
from .utilities.ipfutils import render_jinja2
|
|
@@ -229,6 +228,22 @@ class IPFabricTransformMap(NetBoxModel):
|
|
|
229
228
|
rel_dict_coalesce[field.target_field] = related_object
|
|
230
229
|
return rel_dict, rel_dict_coalesce
|
|
231
230
|
|
|
231
|
+
def strip_source_data(self, source_data: dict) -> dict:
|
|
232
|
+
"""Strip data according to Transform Map mappings but without rendering templates."""
|
|
233
|
+
keys = set()
|
|
234
|
+
for field in self.field_maps.all():
|
|
235
|
+
keys.add(field.source_field)
|
|
236
|
+
if field.template:
|
|
237
|
+
keys.update(
|
|
238
|
+
re.findall(r"object\.([a-zA-Z_0-9]+)(?=.*)", field.template)
|
|
239
|
+
)
|
|
240
|
+
for field in self.relationship_maps.all():
|
|
241
|
+
if field.template:
|
|
242
|
+
keys.update(
|
|
243
|
+
re.findall(r"object\.([a-zA-Z_0-9]+)(?=.*)", field.template)
|
|
244
|
+
)
|
|
245
|
+
return {k: source_data[k] for k in keys}
|
|
246
|
+
|
|
232
247
|
def get_context(self, source_data):
|
|
233
248
|
new_data = deepcopy(source_data)
|
|
234
249
|
relationship, coalesce_relationship = self.build_relationships(
|
|
@@ -241,7 +256,8 @@ class IPFabricTransformMap(NetBoxModel):
|
|
|
241
256
|
context = self.render(new_data)
|
|
242
257
|
return context
|
|
243
258
|
|
|
244
|
-
def update_or_create_instance(self, context, tags=
|
|
259
|
+
def update_or_create_instance(self, context, tags=None, connection_name=None):
|
|
260
|
+
tags = tags or []
|
|
245
261
|
target_class = self.target_model.model_class()
|
|
246
262
|
queryset = target_class.objects.using(connection_name)
|
|
247
263
|
|
|
@@ -272,8 +288,11 @@ class IPFabricTransformMap(NetBoxModel):
|
|
|
272
288
|
if field.endswith("__isnull"):
|
|
273
289
|
context[field[:-8]] = None
|
|
274
290
|
del context[field]
|
|
275
|
-
|
|
291
|
+
# Using queryset.create() creates the object even when it fails on clean()
|
|
292
|
+
# To to work around it, we do it in two steps to avoid saving it to DB before clean()
|
|
293
|
+
instance = queryset.model(**context, **defaults)
|
|
276
294
|
instance.full_clean()
|
|
295
|
+
instance.save(using=connection_name)
|
|
277
296
|
|
|
278
297
|
apply_tags(instance, tags, connection_name)
|
|
279
298
|
|
|
@@ -867,9 +886,17 @@ class IPFabricSync(IPFabricClient, JobsMixin, TagsMixin, ChangeLoggedModel):
|
|
|
867
886
|
try:
|
|
868
887
|
active_branch.set(branch)
|
|
869
888
|
try:
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
889
|
+
try:
|
|
890
|
+
signals.post_save.connect(
|
|
891
|
+
assign_primary_mac_address, sender=MACAddress
|
|
892
|
+
)
|
|
893
|
+
runner.collect_and_sync(
|
|
894
|
+
ingestion=IPFabricIngestion.objects.get(pk=ingestion.pk)
|
|
895
|
+
)
|
|
896
|
+
finally:
|
|
897
|
+
signals.post_save.disconnect(
|
|
898
|
+
assign_primary_mac_address, sender=MACAddress
|
|
899
|
+
)
|
|
873
900
|
finally:
|
|
874
901
|
active_branch.set(None)
|
|
875
902
|
finally:
|
|
@@ -1002,15 +1029,7 @@ class IPFabricIngestion(JobsMixin, models.Model):
|
|
|
1002
1029
|
# Begin Sync
|
|
1003
1030
|
logger.debug(f"Merging {self.name}")
|
|
1004
1031
|
try:
|
|
1005
|
-
signals.pre_save.connect(clear_other_primary_ip, sender=Device)
|
|
1006
|
-
signals.post_save.disconnect(
|
|
1007
|
-
assign_virtualchassis_master, sender=VirtualChassis
|
|
1008
|
-
)
|
|
1009
1032
|
self.branch.merge(user=self.sync.user)
|
|
1010
|
-
signals.post_save.connect(
|
|
1011
|
-
assign_virtualchassis_master, sender=VirtualChassis
|
|
1012
|
-
)
|
|
1013
|
-
signals.pre_save.disconnect(clear_other_primary_ip, sender=Device)
|
|
1014
1033
|
ipfabricsync.status = IPFabricSyncStatusChoices.COMPLETED
|
|
1015
1034
|
except Exception as e:
|
|
1016
1035
|
ipfabricsync.status = IPFabricSyncStatusChoices.FAILED
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from dcim.models import Interface
|
|
4
|
+
from dcim.models import MACAddress
|
|
5
|
+
from netbox_branching.contextvars import active_branch
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger("ipfabric_netbox.utilities.ipf_utils")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def assign_primary_mac_address(instance: MACAddress, **kwargs) -> None:
|
|
11
|
+
try:
|
|
12
|
+
if instance.assigned_object and instance.assigned_object.primary_mac_address:
|
|
13
|
+
# The Interface already has primary MAC, nothing to do
|
|
14
|
+
return
|
|
15
|
+
except Interface.DoesNotExist:
|
|
16
|
+
# The Interface is not created yet, cannot be assigned
|
|
17
|
+
return
|
|
18
|
+
|
|
19
|
+
connection_name = None
|
|
20
|
+
if branch := active_branch.get():
|
|
21
|
+
connection_name = branch.connection_name
|
|
22
|
+
instance.assigned_object.snapshot()
|
|
23
|
+
instance.assigned_object.primary_mac_address = instance
|
|
24
|
+
instance.assigned_object.save(using=connection_name)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def remove_group_from_syncs(instance, **kwargs):
|
|
28
|
+
"""
|
|
29
|
+
When an IPFabricTransformMapGroup is deleted, remove its ID from any IPFabricSync.parameters['groups'] list.
|
|
30
|
+
"""
|
|
31
|
+
from ipfabric_netbox.models import IPFabricSync
|
|
32
|
+
|
|
33
|
+
group_id = instance.pk
|
|
34
|
+
for sync in IPFabricSync.objects.all():
|
|
35
|
+
params = sync.parameters or {}
|
|
36
|
+
groups = params.get("groups", [])
|
|
37
|
+
if group_id not in groups:
|
|
38
|
+
continue
|
|
39
|
+
params["groups"] = [gid for gid in groups if gid != group_id]
|
|
40
|
+
sync.parameters = params
|
|
41
|
+
sync.save()
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<div class="row mb-3">
|
|
10
10
|
<div class="col-6">
|
|
11
11
|
<div class="card">
|
|
12
|
-
<h5 class="card-header"
|
|
12
|
+
<h5 class="card-header">{% trans "Pre-Change Data" %}</h5>
|
|
13
13
|
<div class="card-body">
|
|
14
14
|
{% if prechange_data %}
|
|
15
15
|
<pre class="change-data">{% for k, v in prechange_data.items %}{% spaceless %}
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
{% endspaceless %}{% endfor %}
|
|
18
18
|
</pre>
|
|
19
19
|
{% else %}
|
|
20
|
-
<span class="text-muted"
|
|
20
|
+
<span class="text-muted">{% trans "None" %}</span>
|
|
21
21
|
{% endif %}
|
|
22
22
|
</div>
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
25
25
|
<div class="col-6">
|
|
26
26
|
<div class="card">
|
|
27
|
-
<h5 class="card-header"
|
|
27
|
+
<h5 class="card-header">{% trans "Post-Change Data" %}</h5>
|
|
28
28
|
<div class="card-body">
|
|
29
29
|
{% if postchange_data %}
|
|
30
30
|
<pre class="change-data">{% for k, v in postchange_data.items %}{% spaceless %}
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
<div class="col">
|
|
39
39
|
<div class="card">
|
|
40
40
|
<div class="card-header">
|
|
41
|
-
<h5 class="d-inline"
|
|
41
|
+
<h5 class="d-inline">{% trans "Difference" %}</h5>
|
|
42
42
|
<!-- <div class="btn-group btn-group-sm float-end noprint">
|
|
43
43
|
<a {% if prev_change %}href="{% url 'extras:objectchange' pk=prev_change.pk %}"{% else %}disabled{% endif %} class="btn btn-outline-secondary">
|
|
44
44
|
<i class="mdi mdi-chevron-left" aria-hidden="true"></i> Previous
|
|
@@ -52,11 +52,11 @@
|
|
|
52
52
|
{% if diff_added == diff_removed %}
|
|
53
53
|
<span class="text-muted" style="margin-left: 10px;">
|
|
54
54
|
{% if object.action == 'create' %}
|
|
55
|
-
%
|
|
55
|
+
{% trans "Object Created" %}
|
|
56
56
|
{% elif object.action == 'delete' %}
|
|
57
|
-
%
|
|
57
|
+
{% trans "Object Deleted" %}
|
|
58
58
|
{% else %}
|
|
59
|
-
%
|
|
59
|
+
{% trans "No Changes" %}
|
|
60
60
|
{% endif %}
|
|
61
61
|
</span>
|
|
62
62
|
{% else %}
|
|
@@ -174,7 +174,10 @@ class IPFabricTransformMapModelTestCase(TestCase):
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
self.site = runner.sync_item(
|
|
177
|
-
|
|
177
|
+
record=runner.create_new_data_record(
|
|
178
|
+
app="dcim", model="site", data=site_data
|
|
179
|
+
),
|
|
180
|
+
cf=sync.update_custom_fields,
|
|
178
181
|
)
|
|
179
182
|
|
|
180
183
|
device_data = {
|
|
@@ -210,37 +213,38 @@ class IPFabricTransformMapModelTestCase(TestCase):
|
|
|
210
213
|
"uptime": 7254180,
|
|
211
214
|
"vendor": "cisco",
|
|
212
215
|
"version": "15.2(4)M1",
|
|
216
|
+
"virtual_chassis": None,
|
|
213
217
|
"slug": None,
|
|
214
218
|
}
|
|
215
219
|
|
|
216
220
|
self.mf_obj = runner.sync_item(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
221
|
+
record=runner.create_new_data_record(
|
|
222
|
+
app="dcim", model="manufacturer", data=device_data
|
|
223
|
+
),
|
|
220
224
|
cf=sync.update_custom_fields,
|
|
221
225
|
)
|
|
222
226
|
self.dt_obj = runner.sync_item(
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
227
|
+
record=runner.create_new_data_record(
|
|
228
|
+
app="dcim", model="devicetype", data=device_data
|
|
229
|
+
),
|
|
226
230
|
cf=sync.update_custom_fields,
|
|
227
231
|
)
|
|
228
232
|
self.platform = runner.sync_item(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
233
|
+
record=runner.create_new_data_record(
|
|
234
|
+
app="dcim", model="platform", data=device_data
|
|
235
|
+
),
|
|
232
236
|
cf=sync.update_custom_fields,
|
|
233
237
|
)
|
|
234
238
|
self.role = runner.sync_item(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
239
|
+
record=runner.create_new_data_record(
|
|
240
|
+
app="dcim", model="devicerole", data=device_data
|
|
241
|
+
),
|
|
238
242
|
cf=sync.update_custom_fields,
|
|
239
243
|
)
|
|
240
244
|
self.device_object = runner.sync_item(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
245
|
+
record=runner.create_new_data_record(
|
|
246
|
+
app="dcim", model="device", data=device_data
|
|
247
|
+
),
|
|
244
248
|
cf=sync.update_custom_fields,
|
|
245
249
|
)
|
|
246
250
|
|
|
@@ -401,6 +405,7 @@ class IPFabricTransformMapModelTestCase(TestCase):
|
|
|
401
405
|
"uptime": 7254180,
|
|
402
406
|
"vendor": "cisco",
|
|
403
407
|
"version": "15.2(4)M1",
|
|
408
|
+
"virtual_chassis": None,
|
|
404
409
|
"slug": None,
|
|
405
410
|
}
|
|
406
411
|
|
|
@@ -415,9 +420,9 @@ class IPFabricTransformMapModelTestCase(TestCase):
|
|
|
415
420
|
transform_field.template = "{{ object.hostname }} - test"
|
|
416
421
|
transform_field.save()
|
|
417
422
|
device_object = runner.sync_item(
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
423
|
+
record=runner.create_new_data_record(
|
|
424
|
+
app="dcim", model="device", data=device_data
|
|
425
|
+
),
|
|
421
426
|
cf=sync.update_custom_fields,
|
|
422
427
|
)
|
|
423
428
|
self.assertEqual(device_object.name, "L21PE152 - test")
|