ipfabric_netbox 3.2.4__py3-none-any.whl → 3.2.4b2__py3-none-any.whl
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.
Potentially problematic release.
This version of ipfabric_netbox might be problematic. Click here for more details.
- ipfabric_netbox/__init__.py +1 -1
- ipfabric_netbox/api/nested_serializers.py +0 -20
- ipfabric_netbox/api/serializers.py +7 -13
- ipfabric_netbox/api/urls.py +2 -2
- ipfabric_netbox/api/views.py +5 -5
- ipfabric_netbox/data/transform_map.json +21 -35
- ipfabric_netbox/filtersets.py +15 -13
- ipfabric_netbox/forms.py +14 -42
- ipfabric_netbox/jobs.py +12 -7
- ipfabric_netbox/migrations/0001_initial.py +1 -1
- ipfabric_netbox/migrations/0001_initial_squashed_0013_switch_to_branching_plugin.py +503 -0
- ipfabric_netbox/migrations/0007_prepare_custom_fields.py +3 -3
- ipfabric_netbox/migrations/0011_update_part_number_DCIM_inventory_item_template.py +57 -0
- ipfabric_netbox/migrations/0012_remove_status_field.py +18 -0
- ipfabric_netbox/migrations/0013_switch_to_branching_plugin.py +270 -0
- ipfabric_netbox/models.py +120 -91
- ipfabric_netbox/navigation.py +1 -1
- ipfabric_netbox/signals.py +9 -2
- ipfabric_netbox/tables.py +40 -46
- ipfabric_netbox/templates/ipfabric_netbox/{ipfabricbranch.html → ipfabricingestion.html} +14 -9
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricsource.html +3 -3
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync.html +3 -3
- ipfabric_netbox/templates/ipfabric_netbox/ipfabricsync_list.html +71 -0
- ipfabric_netbox/templates/ipfabric_netbox/ipfabrictransformmap.html +0 -4
- ipfabric_netbox/templates/ipfabric_netbox/partials/ingestion_all.html +10 -0
- ipfabric_netbox/templates/ipfabric_netbox/partials/{branch_progress.html → ingestion_progress.html} +1 -1
- ipfabric_netbox/templates/ipfabric_netbox/partials/sync_last_ingestion.html +1 -0
- ipfabric_netbox/urls.py +13 -3
- ipfabric_netbox/utilities/ipfutils.py +52 -19
- ipfabric_netbox/views.py +162 -155
- {ipfabric_netbox-3.2.4.dist-info → ipfabric_netbox-3.2.4b2.dist-info}/METADATA +12 -4
- {ipfabric_netbox-3.2.4.dist-info → ipfabric_netbox-3.2.4b2.dist-info}/RECORD +34 -31
- {ipfabric_netbox-3.2.4.dist-info → ipfabric_netbox-3.2.4b2.dist-info}/WHEEL +1 -1
- ipfabric_netbox/templates/ipfabric_netbox/inc/sync_delete.html +0 -19
- ipfabric_netbox/templates/ipfabric_netbox/partials/branch_all.html +0 -10
- ipfabric_netbox/templates/ipfabric_netbox/partials/sync_last_branch.html +0 -1
- ipfabric_netbox/templates/ipfabric_netbox/sync_list.html +0 -126
- /ipfabric_netbox/templates/ipfabric_netbox/partials/{branch_status.html → ingestion_status.html} +0 -0
ipfabric_netbox/tables.py
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import django_tables2 as tables
|
|
2
|
+
from django.utils.html import format_html
|
|
2
3
|
from django.utils.translation import gettext_lazy as _
|
|
3
4
|
from django_tables2 import Column
|
|
4
|
-
from extras.models import StagedChange
|
|
5
5
|
from netbox.tables import columns
|
|
6
6
|
from netbox.tables import NetBoxTable
|
|
7
|
+
from netbox_branching.models import ChangeDiff
|
|
7
8
|
|
|
8
|
-
from .models import IPFabricBranch
|
|
9
9
|
from .models import IPFabricData
|
|
10
|
+
from .models import IPFabricIngestion
|
|
10
11
|
from .models import IPFabricRelationshipField
|
|
11
12
|
from .models import IPFabricSnapshot
|
|
12
13
|
from .models import IPFabricSource
|
|
@@ -17,7 +18,7 @@ from .models import IPFabricTransformMap
|
|
|
17
18
|
|
|
18
19
|
DIFF_BUTTON = """
|
|
19
20
|
<a href="#"
|
|
20
|
-
hx-get="{% url 'plugins:ipfabric_netbox:
|
|
21
|
+
hx-get="{% url 'plugins:ipfabric_netbox:ipfabricingestion_change_diff' pk=record.branch.pk change_pk=record.pk %}"
|
|
21
22
|
hx-target="#htmx-modal-content"
|
|
22
23
|
data-bs-toggle="modal"
|
|
23
24
|
data-bs-target="#htmx-modal"
|
|
@@ -64,23 +65,21 @@ class IPFabricTransformMapTable(NetBoxTable):
|
|
|
64
65
|
|
|
65
66
|
class Meta(NetBoxTable.Meta):
|
|
66
67
|
model = IPFabricTransformMap
|
|
67
|
-
fields = ("name", "source_model", "target_model"
|
|
68
|
-
default_columns = ("name", "source_model", "target_model"
|
|
68
|
+
fields = ("name", "source_model", "target_model")
|
|
69
|
+
default_columns = ("name", "source_model", "target_model")
|
|
69
70
|
|
|
70
71
|
|
|
71
|
-
class
|
|
72
|
+
class IPFabricIngestionTable(NetBoxTable):
|
|
72
73
|
name = tables.Column(linkify=True)
|
|
73
74
|
sync = tables.Column(verbose_name="IP Fabric Sync", linkify=True)
|
|
75
|
+
branch = tables.Column(linkify=True)
|
|
74
76
|
changes = tables.Column(accessor="staged_changes", verbose_name="Number of Changes")
|
|
75
77
|
actions = columns.ActionsColumn(actions=("delete",))
|
|
76
78
|
|
|
77
|
-
def render_changes(self, value):
|
|
78
|
-
return value.count()
|
|
79
|
-
|
|
80
79
|
class Meta(NetBoxTable.Meta):
|
|
81
|
-
model =
|
|
82
|
-
fields = ("name", "sync", "description", "user", "changes")
|
|
83
|
-
default_columns = ("name", "sync", "description", "user", "changes")
|
|
80
|
+
model = IPFabricIngestion
|
|
81
|
+
fields = ("name", "sync", "branch", "description", "user", "changes")
|
|
82
|
+
default_columns = ("name", "sync", "branch", "description", "user", "changes")
|
|
84
83
|
|
|
85
84
|
|
|
86
85
|
class IPFabricSnapshotTable(NetBoxTable):
|
|
@@ -142,51 +141,46 @@ class SyncTable(NetBoxTable):
|
|
|
142
141
|
default_columns = ("id", "status", "snapshot_name")
|
|
143
142
|
|
|
144
143
|
|
|
145
|
-
class
|
|
146
|
-
# There is no view for single
|
|
144
|
+
class IPFabricIngestionChangesTable(NetBoxTable):
|
|
145
|
+
# There is no view for single change, remove the link in ID
|
|
147
146
|
id = tables.Column(verbose_name=_("ID"))
|
|
148
147
|
pk = None
|
|
149
148
|
object_type = tables.Column(
|
|
150
149
|
accessor="object_type.model", verbose_name="Object Type"
|
|
151
150
|
)
|
|
152
|
-
|
|
151
|
+
object = tables.Column(verbose_name="Object")
|
|
153
152
|
actions = columns.TemplateColumn(template_code=DIFF_BUTTON)
|
|
154
153
|
|
|
155
|
-
def
|
|
156
|
-
|
|
157
|
-
"
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
"
|
|
161
|
-
"
|
|
162
|
-
"
|
|
163
|
-
"
|
|
164
|
-
"
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
name = f"{record.data['prefix']} ({record.data['vrf']})"
|
|
177
|
-
elif value == "MAC address":
|
|
178
|
-
name = record.data["mac_address"]
|
|
179
|
-
else:
|
|
180
|
-
name = record.data
|
|
154
|
+
def render_object(self, value, record):
|
|
155
|
+
model_templates = {
|
|
156
|
+
"Device": lambda v: v.name,
|
|
157
|
+
"DeviceRole": lambda v: v.name,
|
|
158
|
+
"DeviceType": lambda v: v.model,
|
|
159
|
+
"IPAddress": lambda v: v.address,
|
|
160
|
+
"Interface": lambda v: f"{v.name} (Device {v.device.name})",
|
|
161
|
+
"InventoryItem": lambda v: f"{v.name} (Device {v.device.name})",
|
|
162
|
+
"MACAddress": lambda v: v.mac_address,
|
|
163
|
+
"Manufacturer": lambda v: v.name,
|
|
164
|
+
"Platform": lambda v: v.name,
|
|
165
|
+
"Prefix": lambda v: f"{v.prefix} (VRF {v.vrf})",
|
|
166
|
+
"Site": lambda v: v.name,
|
|
167
|
+
"VirtualChassis": lambda v: v.name,
|
|
168
|
+
"VLAN": lambda v: f"{v.name} (VID {v.vid})",
|
|
169
|
+
"VRF": lambda v: v.name,
|
|
170
|
+
}
|
|
171
|
+
if value and (class_name := value.__class__.__name__) in model_templates:
|
|
172
|
+
field_value = model_templates[class_name](value)
|
|
173
|
+
if url := value.get_absolute_url():
|
|
174
|
+
return format_html("<a href={}>{}</a>", url, field_value)
|
|
181
175
|
else:
|
|
182
|
-
|
|
183
|
-
return
|
|
176
|
+
field_value = record.object_repr
|
|
177
|
+
return field_value
|
|
184
178
|
|
|
185
179
|
class Meta(NetBoxTable.Meta):
|
|
186
|
-
model =
|
|
180
|
+
model = ChangeDiff
|
|
187
181
|
name = "staged_changes"
|
|
188
|
-
fields = ("
|
|
189
|
-
default_columns = ("
|
|
182
|
+
fields = ("object", "action", "object_type", "actions")
|
|
183
|
+
default_columns = ("object", "action", "object_type", "actions")
|
|
190
184
|
|
|
191
185
|
|
|
192
186
|
class DeviceIPFTable(tables.Table):
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
{% if perms.core.sync_datasource %}
|
|
18
18
|
{% if object.sync.ready_for_sync %}
|
|
19
19
|
<a href="#"
|
|
20
|
-
hx-get="{% url 'plugins:ipfabric_netbox:
|
|
20
|
+
hx-get="{% url 'plugins:ipfabric_netbox:ipfabricingestion_merge' pk=object.pk %}"
|
|
21
21
|
hx-target="#htmx-modal-content"
|
|
22
22
|
class="btn btn-success"
|
|
23
23
|
data-bs-toggle="modal"
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
<div class="row mb-3">
|
|
42
42
|
<div class="col col-md-6">
|
|
43
43
|
<div class="card">
|
|
44
|
-
<h5 class="card-header">
|
|
44
|
+
<h5 class="card-header">Ingestion Information</h5>
|
|
45
45
|
<div class="card-body">
|
|
46
46
|
<table class="table table-hover attr-table">
|
|
47
47
|
<tr>
|
|
@@ -57,13 +57,18 @@
|
|
|
57
57
|
<td><a href="{% url 'plugins:ipfabric_netbox:ipfabricsync' pk=object.sync.pk %}">{{ object.sync.name}}</a>
|
|
58
58
|
</td>
|
|
59
59
|
</tr>
|
|
60
|
+
<tr>
|
|
61
|
+
<th scope="row">Branch Object</th>
|
|
62
|
+
<td>{% if object.branch %}<a href="{% url 'plugins:netbox_branching:branch' pk=object.branch.pk %}">{{ object.branch.name}}</a>{% else %}{{ ""|placeholder }}{% endif %}
|
|
63
|
+
</td>
|
|
64
|
+
</tr>
|
|
60
65
|
<tr>
|
|
61
66
|
<th scope="row">Job Object</th>
|
|
62
67
|
<td><a href="{% url 'core:job' pk=object.job.pk %}">{{ object.job }}</a></td>
|
|
63
68
|
</tr>
|
|
64
69
|
<tr>
|
|
65
|
-
<th scope="row">
|
|
66
|
-
<td><div id="
|
|
70
|
+
<th scope="row">Ingestion Status</th>
|
|
71
|
+
<td><div id="ingestion_status" hx-swap-oob="true">{% include 'ipfabric_netbox/partials/ingestion_status.html' with object=object %}</div></td>
|
|
67
72
|
<!-- <td >{% badge object.sync.get_status_display bg_color=object.sync.get_status_color %}</td> -->
|
|
68
73
|
</div>
|
|
69
74
|
</tr>
|
|
@@ -120,8 +125,8 @@
|
|
|
120
125
|
</table>
|
|
121
126
|
</div>
|
|
122
127
|
</div>
|
|
123
|
-
<div id="
|
|
124
|
-
{% include 'ipfabric_netbox/partials/
|
|
128
|
+
<div id="ingestion_progress">
|
|
129
|
+
{% include 'ipfabric_netbox/partials/ingestion_progress.html' %}
|
|
125
130
|
</div>
|
|
126
131
|
<!-- {% include 'inc/panels/related_objects.html' %} -->
|
|
127
132
|
{% include 'inc/panels/custom_fields.html' %}
|
|
@@ -129,10 +134,10 @@
|
|
|
129
134
|
</div>
|
|
130
135
|
</div>
|
|
131
136
|
<div class="row mb-3">
|
|
132
|
-
<div class="col col-md-12" {% if not object.job.completed %} hx-get="{% url 'plugins:ipfabric_netbox:
|
|
133
|
-
hx-trigger="every 5s" hx-target="#
|
|
137
|
+
<div class="col col-md-12" {% if not object.job.completed %} hx-get="{% url 'plugins:ipfabric_netbox:ipfabricingestion_logs' pk=object.pk %}?type=info"
|
|
138
|
+
hx-trigger="every 5s" hx-target="#ingestion_logs" hx-select="#ingestion_logs" hx-select-oob="#ingestion_status:innerHTML"
|
|
134
139
|
hx-swap="innerHTML" {% endif %}>
|
|
135
|
-
<div id="
|
|
140
|
+
<div id="ingestion_logs">{% include 'ipfabric_netbox/partials/job_logs.html' with job=object.job %}</div>
|
|
136
141
|
</div>
|
|
137
142
|
{% endblock content %}
|
|
138
143
|
|
|
@@ -102,9 +102,9 @@
|
|
|
102
102
|
{% plugin_right_page object %}
|
|
103
103
|
</div>
|
|
104
104
|
{% if job %}
|
|
105
|
-
<div class="col col-md-12" {% if not object.job.completed %} hx-trigger="every 5s" hx-target="#
|
|
106
|
-
hx-select="#
|
|
107
|
-
<div id="
|
|
105
|
+
<div class="col col-md-12" {% if not object.job.completed %} hx-trigger="every 5s" hx-target="#ingestion_logs"
|
|
106
|
+
hx-select="#ingestion_logs" hx-select-oob="#ingestion_status:innerHTML" hx-swap="innerHTML" {% endif %}>
|
|
107
|
+
<div id="ingestionlogs">{% include 'ipfabric_netbox/partials/job_logs.html' with job=job %}</div>
|
|
108
108
|
</div>
|
|
109
109
|
{% endif %}
|
|
110
110
|
</div>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
{% load render_table from django_tables2 %}
|
|
6
6
|
|
|
7
7
|
{% block extra_controls %}
|
|
8
|
-
{% if perms.ipfabric_netbox.
|
|
8
|
+
{% if perms.ipfabric_netbox.start_sync %}
|
|
9
9
|
{% if object.ready_for_sync %}
|
|
10
10
|
<form action="{% url 'plugins:ipfabric_netbox:ipfabricsync_sync' pk=object.pk %}" method="post">
|
|
11
11
|
{% csrf_token %}
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
<td>{{ object.last_synced|placeholder }}</td>
|
|
52
52
|
</tr>
|
|
53
53
|
<tr>
|
|
54
|
-
<th scope="row">Latest
|
|
55
|
-
<td><div {% if object.status == 'queued' or object.status == 'syncing' %} hx-get="{% url 'plugins:ipfabric_netbox:ipfabricsync' pk=object.pk %}" hx-trigger="every 1s"{% endif %}>{% include 'ipfabric_netbox/partials/
|
|
54
|
+
<th scope="row">Latest ingestion</th>
|
|
55
|
+
<td><div {% if object.status == 'queued' or object.status == 'syncing' %} hx-get="{% url 'plugins:ipfabric_netbox:ipfabricsync' pk=object.pk %}" hx-trigger="every 1s"{% endif %}>{% include 'ipfabric_netbox/partials/sync_last_ingestion.html' %}</div></td>
|
|
56
56
|
</tr>
|
|
57
57
|
<tr>
|
|
58
58
|
<th scope="row">Schedule</th>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{% extends 'base/layout.html' %}
|
|
2
|
+
{% load buttons %}
|
|
3
|
+
{% load helpers %}
|
|
4
|
+
{% load perms %}
|
|
5
|
+
|
|
6
|
+
{% block title %}Ingestion{% endblock %}
|
|
7
|
+
|
|
8
|
+
{% block tabs %}
|
|
9
|
+
<ul class="nav nav-tabs px-3">
|
|
10
|
+
<li class="nav-item" role="presentation">
|
|
11
|
+
<a class="nav-link active" role="tab">Ingestion</a>
|
|
12
|
+
</li>
|
|
13
|
+
</ul>
|
|
14
|
+
{% endblock tabs %}
|
|
15
|
+
|
|
16
|
+
{% block controls %}
|
|
17
|
+
<div class="controls">
|
|
18
|
+
<div class="control-group">
|
|
19
|
+
{% block extra_controls %}{% endblock %}
|
|
20
|
+
{% add_button model %}
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
{% endblock controls %}
|
|
24
|
+
|
|
25
|
+
{% block content %}
|
|
26
|
+
<div class="tab-content">
|
|
27
|
+
{% for sync in syncs %}
|
|
28
|
+
<div class="card">
|
|
29
|
+
<h5 class="card-header d-flex justify-content-between" id="module{{ module.pk }}">
|
|
30
|
+
<div>
|
|
31
|
+
<i class="mdi mdi-cloud-sync"></i><i class="mdi mdi-sync"></i> <a href="{% url 'plugins:ipfabric_netbox:ipfabricsync' pk=sync.pk %}">{{ sync.name}}</a>
|
|
32
|
+
</div>
|
|
33
|
+
{% if perms.ipfabric_netbox.delete_ipfabricsync %}
|
|
34
|
+
<a href="{% url 'plugins:ipfabric_netbox:ipfabricsync_delete' pk=sync.pk %}" class="btn btn-danger btn-sm">
|
|
35
|
+
<i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> Delete
|
|
36
|
+
</a>
|
|
37
|
+
{% endif %}
|
|
38
|
+
|
|
39
|
+
</h5>
|
|
40
|
+
<div class="card-body">
|
|
41
|
+
{% include 'inc/sync_warning.html' with object=module %}
|
|
42
|
+
<table class="table table-hover table-headings reports">
|
|
43
|
+
<thead>
|
|
44
|
+
<tr>
|
|
45
|
+
<th width="200">Source</th>
|
|
46
|
+
<th width="400">Snapshot</th>
|
|
47
|
+
<th>Status</th>
|
|
48
|
+
<th>Last Run</th>
|
|
49
|
+
</tr>
|
|
50
|
+
</thead>
|
|
51
|
+
<tbody>
|
|
52
|
+
<tr>
|
|
53
|
+
<td><a href="{% url 'plugins:ipfabric_netbox:ipfabricsource' pk=sync.snapshot_data.source.pk %}">{{ sync.snapshot_data.source.name }}</a></td>
|
|
54
|
+
<td><a href="{% url 'plugins:ipfabric_netbox:ipfabricsnapshot' pk=sync.snapshot_data.pk %}">{{ sync.snapshot_data.name}}</a></td>
|
|
55
|
+
<td>{% badge sync.get_status_display last_job.get_status_color %}</td>
|
|
56
|
+
<td>{{sync.last_synced}}</td>
|
|
57
|
+
</tr>
|
|
58
|
+
</tbody>
|
|
59
|
+
</table>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
{% empty %}
|
|
63
|
+
<div class="alert alert-info" role="alert">
|
|
64
|
+
<h4 class="alert-heading">Sync Jobs Settings Found</h4>
|
|
65
|
+
{% if perms.extras.add_reportmodule %}
|
|
66
|
+
Get started by <a href="{% url 'plugins:ipfabric_netbox:ipfabricsync_add' %}">creating a sync</a> from an IP Fabric source.
|
|
67
|
+
{% endif %}
|
|
68
|
+
</div>
|
|
69
|
+
{% endfor %}
|
|
70
|
+
</div>
|
|
71
|
+
{% endblock content %}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
|
|
2
|
+
<div id="ingestion_status" hx-swap-oob="true">
|
|
3
|
+
{% include 'ipfabric_netbox/partials/ingestion_status.html' with object=object %}
|
|
4
|
+
</div>
|
|
5
|
+
<div id="ingestion_progress" hx-swap-oob="true">
|
|
6
|
+
{% include 'ipfabric_netbox/partials/ingestion_progress.html' %}
|
|
7
|
+
</div>
|
|
8
|
+
<div id="ingestion_logs">
|
|
9
|
+
{% include 'ipfabric_netbox/partials/job_logs.html' with job_results=job_results job=job %}
|
|
10
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{% if last_ingestion %}<a href="{% url 'plugins:ipfabric_netbox:ipfabricingestion' pk=last_ingestion.pk %}">{{ last_ingestion.name }}{% else %}{{ ""|placeholder }}{% endif %}</a>
|
ipfabric_netbox/urls.py
CHANGED
|
@@ -63,10 +63,20 @@ urlpatterns = (
|
|
|
63
63
|
path("sync/", views.IPFabricSyncListView.as_view(), name="ipfabricsync_list"),
|
|
64
64
|
path("sync/add/", views.IPFabricSyncEditView.as_view(), name="ipfabricsync_add"),
|
|
65
65
|
path("sync/<int:pk>/", include(get_model_urls("ipfabric_netbox", "ipfabricsync"))),
|
|
66
|
-
# Branch
|
|
67
|
-
path("branch/", views.IPFabricBranchListView.as_view(), name="ipfabricbranch_list"),
|
|
68
66
|
path(
|
|
69
|
-
"
|
|
67
|
+
"sync/<int:pk>/delete/",
|
|
68
|
+
views.IPFabricSyncDeleteView.as_view(),
|
|
69
|
+
name="ipfabricsync_delete",
|
|
70
|
+
),
|
|
71
|
+
# Ingestion
|
|
72
|
+
path(
|
|
73
|
+
"ingestion/",
|
|
74
|
+
views.IPFabricIngestionListView.as_view(),
|
|
75
|
+
name="ipfabricingestion_list",
|
|
76
|
+
),
|
|
77
|
+
path(
|
|
78
|
+
"ingestion/<int:pk>/",
|
|
79
|
+
include(get_model_urls("ipfabric_netbox", "ipfabricingestion")),
|
|
70
80
|
),
|
|
71
81
|
# Transform Map
|
|
72
82
|
path(
|
|
@@ -28,7 +28,7 @@ from .nbutils import order_devices
|
|
|
28
28
|
from .nbutils import order_members
|
|
29
29
|
|
|
30
30
|
if TYPE_CHECKING:
|
|
31
|
-
from ..models import
|
|
31
|
+
from ..models import IPFabricIngestion
|
|
32
32
|
|
|
33
33
|
logger = logging.getLogger("ipfabric_netbox.utilities.ipf_utils")
|
|
34
34
|
|
|
@@ -128,10 +128,16 @@ class IPFabric(object):
|
|
|
128
128
|
|
|
129
129
|
class IPFabricSyncRunner(object):
|
|
130
130
|
def __init__(
|
|
131
|
-
self,
|
|
131
|
+
self,
|
|
132
|
+
transform_map,
|
|
133
|
+
sync=None,
|
|
134
|
+
client: IPFabric = None,
|
|
135
|
+
ingestion=None,
|
|
136
|
+
settings: dict = None,
|
|
132
137
|
) -> None:
|
|
133
138
|
self.client = client
|
|
134
139
|
self.settings = settings
|
|
140
|
+
self.ingestion = ingestion
|
|
135
141
|
self.transform_map = transform_map
|
|
136
142
|
self.sync = sync
|
|
137
143
|
if hasattr(self.sync, "logger"):
|
|
@@ -153,17 +159,17 @@ class IPFabricSyncRunner(object):
|
|
|
153
159
|
if isinstance(err, SearchError):
|
|
154
160
|
if self.settings.get(err.model):
|
|
155
161
|
self.logger.log_failure(
|
|
156
|
-
f"Aborting syncing {err.model} instance due to above error, please check your transform maps and/or existing data.",
|
|
162
|
+
f"Aborting syncing `{err.model}` instance due to above error, please check your transform maps and/or existing data.",
|
|
157
163
|
obj=self.sync,
|
|
158
164
|
)
|
|
159
165
|
else:
|
|
160
166
|
self.logger.log_failure(
|
|
161
|
-
f"Syncing {err.model} is disabled in settings, but hit above error trying to find the correct item. Please check your transform maps and/or existing data.",
|
|
167
|
+
f"Syncing `{err.model}` is disabled in settings, but hit above error trying to find the correct item. Please check your transform maps and/or existing data.",
|
|
162
168
|
obj=self.sync,
|
|
163
169
|
)
|
|
164
170
|
else:
|
|
165
171
|
self.logger.log_failure(
|
|
166
|
-
f"Syncing failed with: {err}
|
|
172
|
+
f"Syncing failed with: `{err}`. See above error for more details.",
|
|
167
173
|
obj=self.sync,
|
|
168
174
|
)
|
|
169
175
|
# Make sure the whole sync is failed when we encounter error
|
|
@@ -172,6 +178,12 @@ class IPFabricSyncRunner(object):
|
|
|
172
178
|
|
|
173
179
|
return wrapper
|
|
174
180
|
|
|
181
|
+
def get_db_connection_name(self) -> str:
|
|
182
|
+
connection_name = None
|
|
183
|
+
if self.ingestion:
|
|
184
|
+
connection_name = self.ingestion.branch.connection_name
|
|
185
|
+
return connection_name
|
|
186
|
+
|
|
175
187
|
def get_model_or_update(self, app, model, data):
|
|
176
188
|
transform_map = self.transform_map.objects.filter(
|
|
177
189
|
target_model__app_label=app, target_model__model=model
|
|
@@ -198,16 +210,18 @@ class IPFabricSyncRunner(object):
|
|
|
198
210
|
|
|
199
211
|
object = None
|
|
200
212
|
try:
|
|
213
|
+
connection_name = self.get_db_connection_name()
|
|
201
214
|
if model_settings:
|
|
202
215
|
logger.info(f"Creating {model}")
|
|
203
216
|
object = transform_map.update_or_create_instance(
|
|
204
217
|
context=context,
|
|
205
218
|
tags=self.sync.tags.all(),
|
|
219
|
+
connection_name=connection_name,
|
|
206
220
|
)
|
|
207
221
|
else:
|
|
208
222
|
logger.info(f"Getting {model}")
|
|
209
223
|
context.pop("defaults", None)
|
|
210
|
-
object = queryset.get(**context)
|
|
224
|
+
object = queryset.using(connection_name).get(**context)
|
|
211
225
|
except queryset.model.DoesNotExist as err:
|
|
212
226
|
self.logger.log_failure(
|
|
213
227
|
f"<b>`{model}` with these keys not found: `{context}`.</b><br/>Original data: `{data}`.",
|
|
@@ -474,6 +488,16 @@ class IPFabricSyncRunner(object):
|
|
|
474
488
|
else:
|
|
475
489
|
managed_ips[ip["sn"]][int_name].append(ip)
|
|
476
490
|
|
|
491
|
+
for vlan in data["vlan"][:]:
|
|
492
|
+
# Remove VLANs with ID 0, minimum VLAN ID in NetBox is 1
|
|
493
|
+
if vlan.get("vlanId") == 0:
|
|
494
|
+
data["vlan"].remove(vlan)
|
|
495
|
+
|
|
496
|
+
for item in data["inventoryitem"][:]:
|
|
497
|
+
# Remove items with empty serial number
|
|
498
|
+
if item.get("sn") in [None, "None"]:
|
|
499
|
+
data["inventoryitem"].remove(item)
|
|
500
|
+
|
|
477
501
|
for model, item_count in [
|
|
478
502
|
("site", len(data.get("site", []))),
|
|
479
503
|
("device", len(devices)),
|
|
@@ -513,7 +537,7 @@ class IPFabricSyncRunner(object):
|
|
|
513
537
|
app_label: str,
|
|
514
538
|
model: str,
|
|
515
539
|
cf: bool = False,
|
|
516
|
-
|
|
540
|
+
ingestion: "IPFabricIngestion" = None,
|
|
517
541
|
) -> None:
|
|
518
542
|
"""Sync list of items to NetBox."""
|
|
519
543
|
if not self.settings.get(model):
|
|
@@ -530,17 +554,18 @@ class IPFabricSyncRunner(object):
|
|
|
530
554
|
self.logger.increment_statistics(model=model)
|
|
531
555
|
|
|
532
556
|
if cf:
|
|
557
|
+
synced_object.snapshot()
|
|
533
558
|
synced_object.custom_field_data[
|
|
534
559
|
"ipfabric_source"
|
|
535
560
|
] = self.sync.snapshot_data.source.pk
|
|
536
|
-
if
|
|
537
|
-
synced_object.custom_field_data["
|
|
561
|
+
if ingestion:
|
|
562
|
+
synced_object.custom_field_data["ipfabric_ingestion"] = ingestion.pk
|
|
538
563
|
synced_object.save()
|
|
539
564
|
|
|
540
565
|
@handle_errors
|
|
541
566
|
def sync_devices(
|
|
542
567
|
self,
|
|
543
|
-
|
|
568
|
+
ingestion,
|
|
544
569
|
devices,
|
|
545
570
|
interface_dict,
|
|
546
571
|
managed_ips,
|
|
@@ -578,11 +603,12 @@ class IPFabricSyncRunner(object):
|
|
|
578
603
|
continue
|
|
579
604
|
|
|
580
605
|
if self.settings.get("device"):
|
|
606
|
+
device_object.snapshot()
|
|
581
607
|
device_object.custom_field_data[
|
|
582
608
|
"ipfabric_source"
|
|
583
609
|
] = self.sync.snapshot_data.source.pk
|
|
584
|
-
if
|
|
585
|
-
device_object.custom_field_data["
|
|
610
|
+
if ingestion:
|
|
611
|
+
device_object.custom_field_data["ipfabric_ingestion"] = ingestion.pk
|
|
586
612
|
device_object.save()
|
|
587
613
|
|
|
588
614
|
self.logger.increment_statistics(model="device")
|
|
@@ -622,20 +648,26 @@ class IPFabricSyncRunner(object):
|
|
|
622
648
|
return
|
|
623
649
|
self.logger.increment_statistics(model="ipaddress")
|
|
624
650
|
|
|
651
|
+
connection_name = self.get_db_connection_name()
|
|
652
|
+
|
|
625
653
|
try:
|
|
626
654
|
# Removing other IP is done in .signals.clear_other_primary_ip
|
|
627
655
|
# But do it here too so the change is shown in StagedChange diff
|
|
628
|
-
other_device = Device.objects.get(
|
|
656
|
+
other_device = Device.objects.using(connection_name).get(
|
|
657
|
+
primary_ip4=ip_address_obj
|
|
658
|
+
)
|
|
629
659
|
if other_device and device_object != other_device:
|
|
660
|
+
other_device.snapshot()
|
|
630
661
|
other_device.primary_ip4 = None
|
|
631
|
-
other_device.save()
|
|
662
|
+
other_device.save(using=connection_name)
|
|
632
663
|
except ObjectDoesNotExist:
|
|
633
664
|
pass
|
|
634
665
|
|
|
635
666
|
if login_ip == primary_ip:
|
|
636
667
|
try:
|
|
668
|
+
device_object.snapshot()
|
|
637
669
|
device_object.primary_ip4 = ip_address_obj
|
|
638
|
-
device_object.save()
|
|
670
|
+
device_object.save(using=connection_name)
|
|
639
671
|
except ValueError as err:
|
|
640
672
|
self.logger.log_failure(
|
|
641
673
|
f"Error assigning primary IP to device: {err}", obj=self.sync
|
|
@@ -659,8 +691,9 @@ class IPFabricSyncRunner(object):
|
|
|
659
691
|
"dcim", "macaddress", macaddress_data
|
|
660
692
|
)
|
|
661
693
|
try:
|
|
694
|
+
interface_object.snapshot()
|
|
662
695
|
interface_object.primary_mac_address = macaddress_object
|
|
663
|
-
interface_object.save()
|
|
696
|
+
interface_object.save(using=self.get_db_connection_name())
|
|
664
697
|
except ValueError as err:
|
|
665
698
|
self.logger.log_failure(
|
|
666
699
|
f"Error assigning MAC Address to interface: {err}", obj=self.sync
|
|
@@ -697,7 +730,7 @@ class IPFabricSyncRunner(object):
|
|
|
697
730
|
|
|
698
731
|
return interface_object
|
|
699
732
|
|
|
700
|
-
def collect_and_sync(self,
|
|
733
|
+
def collect_and_sync(self, ingestion=None) -> None:
|
|
701
734
|
self.logger.log_info("Starting data sync.", obj=self.sync)
|
|
702
735
|
(
|
|
703
736
|
sites,
|
|
@@ -711,10 +744,10 @@ class IPFabricSyncRunner(object):
|
|
|
711
744
|
) = self.collect_data()
|
|
712
745
|
|
|
713
746
|
self.sync_items(
|
|
714
|
-
app_label="dcim", model="site", items=sites, cf=True,
|
|
747
|
+
app_label="dcim", model="site", items=sites, cf=True, ingestion=ingestion
|
|
715
748
|
)
|
|
716
749
|
self.sync_devices(
|
|
717
|
-
|
|
750
|
+
ingestion,
|
|
718
751
|
devices,
|
|
719
752
|
interface_dict,
|
|
720
753
|
managed_ips,
|