netbox-plugin-dns 1.1.0b2__py3-none-any.whl → 1.1.0b4__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 netbox-plugin-dns might be problematic. Click here for more details.
- netbox_dns/__init__.py +7 -6
- netbox_dns/filtersets/record.py +1 -1
- netbox_dns/forms/view.py +91 -4
- netbox_dns/forms/zone.py +9 -4
- netbox_dns/graphql/types.py +2 -0
- netbox_dns/management/commands/{setup_autodns.py → setup_dnssync.py} +19 -15
- netbox_dns/models/record.py +23 -14
- netbox_dns/models/zone.py +10 -5
- netbox_dns/signals/{ipam_autodns.py → ipam_dnssync.py} +72 -22
- netbox_dns/tables/ipam_dnssync.py +11 -0
- netbox_dns/template_content.py +14 -2
- netbox_dns/templates/netbox_dns/view/button.html +9 -0
- netbox_dns/templates/netbox_dns/view/prefix.html +41 -0
- netbox_dns/templates/netbox_dns/view.html +1 -1
- netbox_dns/urls/view.py +6 -0
- netbox_dns/utilities/__init__.py +1 -1
- netbox_dns/utilities/{ipam_autodns.py → ipam_dnssync.py} +55 -31
- netbox_dns/views/record.py +2 -2
- netbox_dns/views/view.py +26 -1
- {netbox_plugin_dns-1.1.0b2.dist-info → netbox_plugin_dns-1.1.0b4.dist-info}/METADATA +1 -1
- {netbox_plugin_dns-1.1.0b2.dist-info → netbox_plugin_dns-1.1.0b4.dist-info}/RECORD +23 -20
- {netbox_plugin_dns-1.1.0b2.dist-info → netbox_plugin_dns-1.1.0b4.dist-info}/LICENSE +0 -0
- {netbox_plugin_dns-1.1.0b2.dist-info → netbox_plugin_dns-1.1.0b4.dist-info}/WHEEL +0 -0
netbox_dns/__init__.py
CHANGED
|
@@ -5,7 +5,7 @@ from ipam.choices import IPAddressStatusChoices
|
|
|
5
5
|
|
|
6
6
|
from netbox_dns.choices import RecordTypeChoices
|
|
7
7
|
|
|
8
|
-
__version__ = "1.1.
|
|
8
|
+
__version__ = "1.1.0b4"
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class DNSConfig(PluginConfig):
|
|
@@ -25,13 +25,13 @@ class DNSConfig(PluginConfig):
|
|
|
25
25
|
"zone_soa_retry": 7200,
|
|
26
26
|
"zone_soa_expire": 2419200,
|
|
27
27
|
"zone_soa_minimum": 3600,
|
|
28
|
-
"
|
|
29
|
-
"
|
|
28
|
+
"dnssync_disabled": False,
|
|
29
|
+
"dnssync_ipaddress_active_status": [
|
|
30
30
|
IPAddressStatusChoices.STATUS_ACTIVE,
|
|
31
31
|
IPAddressStatusChoices.STATUS_DHCP,
|
|
32
32
|
IPAddressStatusChoices.STATUS_SLAAC,
|
|
33
33
|
],
|
|
34
|
-
"
|
|
34
|
+
"dnssync_conflict_deactivate": False,
|
|
35
35
|
"tolerate_characters_in_zone_labels": "",
|
|
36
36
|
"tolerate_underscores_in_labels": False,
|
|
37
37
|
"tolerate_underscores_in_hostnames": False, # Deprecated, will be removed in 1.2.0
|
|
@@ -51,8 +51,9 @@ class DNSConfig(PluginConfig):
|
|
|
51
51
|
def ready(self):
|
|
52
52
|
super().ready()
|
|
53
53
|
|
|
54
|
-
if not settings.PLUGINS_CONFIG["netbox_dns"].get("
|
|
55
|
-
|
|
54
|
+
if not settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
|
|
55
|
+
import netbox_dns.signals.ipam_dnssync
|
|
56
|
+
import netbox_dns.tables.ipam_dnssync
|
|
56
57
|
|
|
57
58
|
|
|
58
59
|
#
|
netbox_dns/filtersets/record.py
CHANGED
|
@@ -121,7 +121,7 @@ class RecordFilterSet(TenancyFilterSet, NetBoxModelFilterSet):
|
|
|
121
121
|
if not value.strip():
|
|
122
122
|
return queryset
|
|
123
123
|
qs_filter = (
|
|
124
|
-
Q(
|
|
124
|
+
Q(fqdn__icontains=value)
|
|
125
125
|
| Q(value__icontains=value)
|
|
126
126
|
| Q(zone__name__icontains=value)
|
|
127
127
|
)
|
netbox_dns/forms/view.py
CHANGED
|
@@ -21,12 +21,12 @@ from utilities.forms.rendering import FieldSet
|
|
|
21
21
|
from tenancy.models import Tenant
|
|
22
22
|
from tenancy.forms import TenancyForm, TenancyFilterForm
|
|
23
23
|
from ipam.models import Prefix
|
|
24
|
+
from netbox.context import current_request
|
|
24
25
|
|
|
25
26
|
from netbox_dns.models import View
|
|
26
27
|
from netbox_dns.fields import PrefixDynamicModelMultipleChoiceField
|
|
27
28
|
from netbox_dns.utilities import (
|
|
28
29
|
check_dns_records,
|
|
29
|
-
update_dns_records,
|
|
30
30
|
get_ip_addresses_by_prefix,
|
|
31
31
|
get_views_by_prefix,
|
|
32
32
|
)
|
|
@@ -37,6 +37,7 @@ __all__ = (
|
|
|
37
37
|
"ViewFilterForm",
|
|
38
38
|
"ViewImportForm",
|
|
39
39
|
"ViewBulkEditForm",
|
|
40
|
+
"ViewPrefixEditForm",
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
|
|
@@ -94,9 +95,24 @@ class ViewForm(ViewPrefixUpdateMixin, TenancyForm, NetBoxModelForm):
|
|
|
94
95
|
def __init__(self, *args, **kwargs):
|
|
95
96
|
super().__init__(*args, **kwargs)
|
|
96
97
|
|
|
97
|
-
if settings.PLUGINS_CONFIG["netbox_dns"].get("
|
|
98
|
+
if settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
|
|
98
99
|
del self.fields["prefixes"]
|
|
99
100
|
|
|
101
|
+
if request := current_request.get():
|
|
102
|
+
if not request.user.has_perm("ipam.view_prefix"):
|
|
103
|
+
self._saved_prefixes = self.initial["prefixes"]
|
|
104
|
+
self.initial["prefixes"] = []
|
|
105
|
+
self.fields["prefixes"].disabled = True
|
|
106
|
+
self.fields["prefixes"].widget.attrs[
|
|
107
|
+
"placeholder"
|
|
108
|
+
] = "You do not have permission to modify assigned prefixes"
|
|
109
|
+
|
|
110
|
+
def clean_prefixes(self):
|
|
111
|
+
if hasattr(self, "_saved_prefixes"):
|
|
112
|
+
return self._saved_prefixes
|
|
113
|
+
|
|
114
|
+
return self.cleaned_data["prefixes"]
|
|
115
|
+
|
|
100
116
|
prefixes = PrefixDynamicModelMultipleChoiceField(
|
|
101
117
|
queryset=Prefix.objects.all(),
|
|
102
118
|
required=False,
|
|
@@ -128,7 +144,7 @@ class ViewFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
|
|
128
144
|
def __init__(self, *args, **kwargs):
|
|
129
145
|
super().__init__(*args, **kwargs)
|
|
130
146
|
|
|
131
|
-
if settings.PLUGINS_CONFIG["netbox_dns"].get("
|
|
147
|
+
if settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
|
|
132
148
|
del self.fields["prefix_id"]
|
|
133
149
|
|
|
134
150
|
model = View
|
|
@@ -164,7 +180,7 @@ class ViewImportForm(ViewPrefixUpdateMixin, NetBoxModelImportForm):
|
|
|
164
180
|
def __init__(self, *args, **kwargs):
|
|
165
181
|
super().__init__(*args, **kwargs)
|
|
166
182
|
|
|
167
|
-
if settings.PLUGINS_CONFIG["netbox_dns"].get("
|
|
183
|
+
if settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
|
|
168
184
|
del self.fields["prefixes"]
|
|
169
185
|
|
|
170
186
|
prefixes = CSVModelMultipleChoiceField(
|
|
@@ -201,3 +217,74 @@ class ViewBulkEditForm(NetBoxModelBulkEditForm):
|
|
|
201
217
|
)
|
|
202
218
|
|
|
203
219
|
nullable_fields = ("description", "tenant")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class ViewPrefixEditForm(forms.ModelForm):
|
|
223
|
+
views = DynamicModelMultipleChoiceField(
|
|
224
|
+
queryset=View.objects.all(),
|
|
225
|
+
required=False,
|
|
226
|
+
label="Assigned DNS Views",
|
|
227
|
+
help_text="Explicitly assigning DNS views overrides all inherited views for this prefix",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
class Meta:
|
|
231
|
+
model = Prefix
|
|
232
|
+
fields = ("views",)
|
|
233
|
+
|
|
234
|
+
def __init__(self, *args, **kwargs):
|
|
235
|
+
super().__init__(*args, **kwargs)
|
|
236
|
+
|
|
237
|
+
self.initial["views"] = self.instance.netbox_dns_views.all()
|
|
238
|
+
self._permission_denied = False
|
|
239
|
+
|
|
240
|
+
if request := current_request.get():
|
|
241
|
+
if not request.user.has_perm("netbox_dns.change_view"):
|
|
242
|
+
self._permission_denied = True
|
|
243
|
+
self.initial["views"] = []
|
|
244
|
+
self.fields["views"].disabled = True
|
|
245
|
+
self.fields["views"].widget.attrs[
|
|
246
|
+
"placeholder"
|
|
247
|
+
] = "You do not have permission to modify assigned views"
|
|
248
|
+
|
|
249
|
+
def clean(self, *args, **kwargs):
|
|
250
|
+
if self._permission_denied:
|
|
251
|
+
return
|
|
252
|
+
|
|
253
|
+
prefix = self.instance
|
|
254
|
+
|
|
255
|
+
super().clean(*args, **kwargs)
|
|
256
|
+
|
|
257
|
+
views = self.cleaned_data.get("views")
|
|
258
|
+
old_views = prefix.netbox_dns_views.all()
|
|
259
|
+
|
|
260
|
+
check_views = View.objects.none()
|
|
261
|
+
|
|
262
|
+
if not views.exists():
|
|
263
|
+
if (parent := prefix.get_parents().last()) is not None:
|
|
264
|
+
check_views = parent.netbox_dns_views.all().difference(old_views)
|
|
265
|
+
|
|
266
|
+
else:
|
|
267
|
+
check_views = views.difference(old_views)
|
|
268
|
+
|
|
269
|
+
for view in check_views:
|
|
270
|
+
try:
|
|
271
|
+
for ip_address in get_ip_addresses_by_prefix(prefix, check_view=False):
|
|
272
|
+
check_dns_records(ip_address, view=view)
|
|
273
|
+
except ValidationError as exc:
|
|
274
|
+
self.add_error("views", exc.messages)
|
|
275
|
+
|
|
276
|
+
def save(self, *args, **kwargs):
|
|
277
|
+
prefix = self.instance
|
|
278
|
+
|
|
279
|
+
if self._permission_denied:
|
|
280
|
+
return prefix
|
|
281
|
+
|
|
282
|
+
old_views = prefix.netbox_dns_views.all()
|
|
283
|
+
views = self.cleaned_data.get("views")
|
|
284
|
+
|
|
285
|
+
for view in views.difference(old_views):
|
|
286
|
+
view.prefixes.add(prefix)
|
|
287
|
+
for view in old_views.difference(views):
|
|
288
|
+
view.prefixes.remove(prefix)
|
|
289
|
+
|
|
290
|
+
return prefix
|
netbox_dns/forms/zone.py
CHANGED
|
@@ -81,6 +81,14 @@ class ZoneTemplateUpdateMixin:
|
|
|
81
81
|
else:
|
|
82
82
|
zone_data = self.cleaned_data.copy()
|
|
83
83
|
|
|
84
|
+
custom_fields = {}
|
|
85
|
+
for key, value in zone_data.copy().items():
|
|
86
|
+
if key.startswith("cf_"):
|
|
87
|
+
custom_fields[key[3:]] = value
|
|
88
|
+
zone_data.pop(key)
|
|
89
|
+
if custom_fields:
|
|
90
|
+
zone_data["custom_field_data"] = custom_fields
|
|
91
|
+
|
|
84
92
|
zone_data.pop("template", None)
|
|
85
93
|
zone_data.pop("tenant_group", None)
|
|
86
94
|
zone_data.pop("_init_time", None)
|
|
@@ -97,10 +105,7 @@ class ZoneTemplateUpdateMixin:
|
|
|
97
105
|
raise RollbackTransaction
|
|
98
106
|
|
|
99
107
|
except ValidationError as exc:
|
|
100
|
-
|
|
101
|
-
template_error = item.value()
|
|
102
|
-
else:
|
|
103
|
-
template_error = [exc]
|
|
108
|
+
self.add_error("template", exc.messages)
|
|
104
109
|
except RollbackTransaction:
|
|
105
110
|
pass
|
|
106
111
|
|
netbox_dns/graphql/types.py
CHANGED
|
@@ -4,6 +4,8 @@ import strawberry
|
|
|
4
4
|
import strawberry_django
|
|
5
5
|
|
|
6
6
|
from netbox.graphql.types import NetBoxObjectType
|
|
7
|
+
from tenancy.graphql.types import TenantType
|
|
8
|
+
from ipam.graphql.types import IPAddressType, PrefixType
|
|
7
9
|
from netbox.graphql.scalars import BigInt
|
|
8
10
|
|
|
9
11
|
from netbox_dns.models import (
|
|
@@ -7,7 +7,7 @@ from ipam.models import IPAddress
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class Command(BaseCommand):
|
|
10
|
-
help = "Setup IPAddress custom fields for IPAM
|
|
10
|
+
help = "Setup IPAddress custom fields for IPAM DNSsync"
|
|
11
11
|
|
|
12
12
|
def add_arguments(self, parser):
|
|
13
13
|
parser.add_argument(
|
|
@@ -19,7 +19,7 @@ class Command(BaseCommand):
|
|
|
19
19
|
|
|
20
20
|
if options.get("remove"):
|
|
21
21
|
if options.get("verbosity"):
|
|
22
|
-
self.stdout.write(
|
|
22
|
+
self.stdout.write("Trying to remove IPAM DNSsync custom fields")
|
|
23
23
|
for cf in (
|
|
24
24
|
"ipaddress_dns_disabled",
|
|
25
25
|
"ipaddress_dns_record_ttl",
|
|
@@ -30,7 +30,7 @@ class Command(BaseCommand):
|
|
|
30
30
|
name=cf, object_types=ipaddress_object_type
|
|
31
31
|
).delete()
|
|
32
32
|
if options.get("verbosity"):
|
|
33
|
-
self.stdout.write(f"
|
|
33
|
+
self.stdout.write(f"Removed custom field '{cf}'")
|
|
34
34
|
except CustomField.DoesNotExist:
|
|
35
35
|
pass
|
|
36
36
|
return
|
|
@@ -39,7 +39,7 @@ class Command(BaseCommand):
|
|
|
39
39
|
# Remove pre-existing IPAM Coupling custom fields
|
|
40
40
|
# -
|
|
41
41
|
if options.get("verbosity") >= 2:
|
|
42
|
-
self.stdout.write(
|
|
42
|
+
self.stdout.write("Trying to remove obsolete IPAM Coupling custom fields")
|
|
43
43
|
for cf in (
|
|
44
44
|
"ipaddress_dns_record_name",
|
|
45
45
|
"ipaddress_dns_zone_id",
|
|
@@ -54,25 +54,25 @@ class Command(BaseCommand):
|
|
|
54
54
|
pass
|
|
55
55
|
|
|
56
56
|
if options.get("verbosity") >= 2:
|
|
57
|
-
self.stdout.write(
|
|
57
|
+
self.stdout.write("Creating IPAM DNSsync custom fields")
|
|
58
58
|
|
|
59
59
|
if not CustomField.objects.filter(
|
|
60
60
|
name="ipaddress_dns_disabled",
|
|
61
61
|
type=CustomFieldTypeChoices.TYPE_BOOLEAN,
|
|
62
62
|
object_types=ipaddress_object_type,
|
|
63
63
|
).exists():
|
|
64
|
-
|
|
64
|
+
cf_dnssync_disabled = CustomField.objects.create(
|
|
65
65
|
name="ipaddress_dns_disabled",
|
|
66
|
-
label="Disable
|
|
66
|
+
label="Disable DNSsync",
|
|
67
67
|
description="Disable DNS address and pointer record generation for this address",
|
|
68
68
|
type=CustomFieldTypeChoices.TYPE_BOOLEAN,
|
|
69
69
|
required=False,
|
|
70
70
|
default=False,
|
|
71
|
-
group_name="
|
|
71
|
+
group_name="DNSsync",
|
|
72
72
|
is_cloneable=True,
|
|
73
73
|
weight=100,
|
|
74
74
|
)
|
|
75
|
-
|
|
75
|
+
cf_dnssync_disabled.object_types.set([ipaddress_object_type])
|
|
76
76
|
if options.get("verbosity"):
|
|
77
77
|
self.stdout.write("Created custom field 'ipaddress_dns_disabled'")
|
|
78
78
|
|
|
@@ -82,8 +82,9 @@ class Command(BaseCommand):
|
|
|
82
82
|
type=CustomFieldTypeChoices.TYPE_INTEGER,
|
|
83
83
|
object_types=ipaddress_object_type,
|
|
84
84
|
)
|
|
85
|
-
if cf_ttl.group_name != "
|
|
86
|
-
cf_ttl.group_name = "
|
|
85
|
+
if cf_ttl.group_name != "DNSsync":
|
|
86
|
+
cf_ttl.group_name = "DNSsync"
|
|
87
|
+
cf_ttl.description = ("TTL for DNS records created for this address",)
|
|
87
88
|
cf_ttl.save()
|
|
88
89
|
if options.get("verbosity"):
|
|
89
90
|
self.stdout.write("Updated custom field 'ipaddress_dns_record_ttl'")
|
|
@@ -96,7 +97,7 @@ class Command(BaseCommand):
|
|
|
96
97
|
validation_minimum=0,
|
|
97
98
|
validation_maximum=2147483647,
|
|
98
99
|
required=False,
|
|
99
|
-
group_name="
|
|
100
|
+
group_name="DNSsync",
|
|
100
101
|
is_cloneable=True,
|
|
101
102
|
weight=200,
|
|
102
103
|
)
|
|
@@ -110,8 +111,11 @@ class Command(BaseCommand):
|
|
|
110
111
|
type=CustomFieldTypeChoices.TYPE_BOOLEAN,
|
|
111
112
|
object_types=ipaddress_object_type,
|
|
112
113
|
)
|
|
113
|
-
if cf_disable_ptr.group_name != "
|
|
114
|
-
cf_disable_ptr.group_name = "
|
|
114
|
+
if cf_disable_ptr.group_name != "DNSsync":
|
|
115
|
+
cf_disable_ptr.group_name = "DNSsync"
|
|
116
|
+
cf_disable_ptr.description = (
|
|
117
|
+
"Disable DNS PTR record generation for this address",
|
|
118
|
+
)
|
|
115
119
|
cf_disable_ptr.save()
|
|
116
120
|
if options.get("verbosity"):
|
|
117
121
|
self.stdout.write(
|
|
@@ -125,7 +129,7 @@ class Command(BaseCommand):
|
|
|
125
129
|
type=CustomFieldTypeChoices.TYPE_BOOLEAN,
|
|
126
130
|
required=False,
|
|
127
131
|
default=False,
|
|
128
|
-
group_name="
|
|
132
|
+
group_name="DNSsync",
|
|
129
133
|
is_cloneable=True,
|
|
130
134
|
weight=300,
|
|
131
135
|
)
|
netbox_dns/models/record.py
CHANGED
|
@@ -58,7 +58,7 @@ def record_data_from_ip_address(ip_address, zone):
|
|
|
58
58
|
RecordStatusChoices.STATUS_ACTIVE
|
|
59
59
|
if ip_address.status
|
|
60
60
|
in settings.PLUGINS_CONFIG["netbox_dns"].get(
|
|
61
|
-
"
|
|
61
|
+
"dnssync_ipaddress_active_status", []
|
|
62
62
|
)
|
|
63
63
|
else RecordStatusChoices.STATUS_INACTIVE
|
|
64
64
|
),
|
|
@@ -461,8 +461,11 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
461
461
|
self.rfc2317_cname_record.delete(save_zone_serial=save_zone_serial)
|
|
462
462
|
self.rfc2317_cname_record = None
|
|
463
463
|
|
|
464
|
-
def update_from_ip_address(self, ip_address):
|
|
465
|
-
|
|
464
|
+
def update_from_ip_address(self, ip_address, zone=None):
|
|
465
|
+
if zone is None:
|
|
466
|
+
zone = self.zone
|
|
467
|
+
|
|
468
|
+
data = record_data_from_ip_address(ip_address, zone)
|
|
466
469
|
|
|
467
470
|
if data is None:
|
|
468
471
|
self.delete()
|
|
@@ -490,9 +493,12 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
490
493
|
**data,
|
|
491
494
|
)
|
|
492
495
|
|
|
493
|
-
def validate_name(self):
|
|
496
|
+
def validate_name(self, new_zone=None):
|
|
497
|
+
if new_zone is None:
|
|
498
|
+
new_zone = self.zone
|
|
499
|
+
|
|
494
500
|
try:
|
|
495
|
-
_zone = dns_name.from_text(
|
|
501
|
+
_zone = dns_name.from_text(new_zone.name, origin=dns_name.root)
|
|
496
502
|
name = dns_name.from_text(self.name, origin=None)
|
|
497
503
|
fqdn = dns_name.from_text(self.name, origin=_zone)
|
|
498
504
|
|
|
@@ -512,7 +518,7 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
512
518
|
if not fqdn.is_subdomain(_zone):
|
|
513
519
|
raise ValidationError(
|
|
514
520
|
{
|
|
515
|
-
"name": f"{self.name} is not a name in {
|
|
521
|
+
"name": f"{self.name} is not a name in {new_zone.name}",
|
|
516
522
|
}
|
|
517
523
|
)
|
|
518
524
|
|
|
@@ -544,15 +550,18 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
544
550
|
except ValidationError as exc:
|
|
545
551
|
raise ValidationError({"value": exc}) from None
|
|
546
552
|
|
|
547
|
-
def check_unique_record(self):
|
|
553
|
+
def check_unique_record(self, new_zone=None):
|
|
548
554
|
if not get_plugin_config("netbox_dns", "enforce_unique_records", False):
|
|
549
555
|
return
|
|
550
556
|
|
|
551
557
|
if not self.is_active:
|
|
552
558
|
return
|
|
553
559
|
|
|
560
|
+
if new_zone is None:
|
|
561
|
+
new_zone = self.zone
|
|
562
|
+
|
|
554
563
|
records = Record.objects.filter(
|
|
555
|
-
zone=
|
|
564
|
+
zone=new_zone,
|
|
556
565
|
name=self.name,
|
|
557
566
|
type=self.type,
|
|
558
567
|
value=self.value,
|
|
@@ -567,7 +576,7 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
567
576
|
if not records.filter(
|
|
568
577
|
ipam_ip_address__isnull=True
|
|
569
578
|
).exists() or get_plugin_config(
|
|
570
|
-
"netbox_dns", "
|
|
579
|
+
"netbox_dns", "dnssync_conflict_deactivate", False
|
|
571
580
|
):
|
|
572
581
|
return
|
|
573
582
|
|
|
@@ -581,7 +590,7 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
581
590
|
if self.ipam_ip_address is None or not self.is_active:
|
|
582
591
|
return
|
|
583
592
|
|
|
584
|
-
if not get_plugin_config("netbox_dns", "
|
|
593
|
+
if not get_plugin_config("netbox_dns", "dnssync_conflict_deactivate", False):
|
|
585
594
|
return
|
|
586
595
|
|
|
587
596
|
records = Record.objects.filter(
|
|
@@ -624,7 +633,7 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
624
633
|
if not records.exists():
|
|
625
634
|
return
|
|
626
635
|
|
|
627
|
-
conflicting_ttls = ", ".join(
|
|
636
|
+
conflicting_ttls = ", ".join({str(record.ttl) for record in records})
|
|
628
637
|
raise ValidationError(
|
|
629
638
|
{
|
|
630
639
|
"ttl": f"There is at least one active {self.type} record for name {self.name} in zone {self.zone} and TTL is different ({conflicting_ttls})."
|
|
@@ -664,10 +673,10 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
664
673
|
self.type = self.type.upper()
|
|
665
674
|
super().clean_fields(*args, **kwargs)
|
|
666
675
|
|
|
667
|
-
def clean(self, *args, **kwargs):
|
|
668
|
-
self.validate_name()
|
|
676
|
+
def clean(self, *args, new_zone=None, **kwargs):
|
|
677
|
+
self.validate_name(new_zone=new_zone)
|
|
669
678
|
self.validate_value()
|
|
670
|
-
self.check_unique_record()
|
|
679
|
+
self.check_unique_record(new_zone=new_zone)
|
|
671
680
|
if self.pk is None:
|
|
672
681
|
self.check_unique_rrset_ttl()
|
|
673
682
|
|
netbox_dns/models/zone.py
CHANGED
|
@@ -654,13 +654,18 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
654
654
|
)
|
|
655
655
|
|
|
656
656
|
if old_zone.name != self.name or old_zone.view != self.view:
|
|
657
|
-
|
|
658
|
-
|
|
657
|
+
for ip_address in get_ip_addresses_by_zone(self):
|
|
658
|
+
try:
|
|
659
|
+
check_dns_records(ip_address, zone=self)
|
|
660
|
+
except ValidationError as exc:
|
|
661
|
+
raise ValidationError(exc.messages)
|
|
662
|
+
|
|
663
|
+
ip_addresses = IPAddress.objects.filter(
|
|
664
|
+
netbox_dns_records__in=self.record_set.filter(
|
|
659
665
|
ipam_ip_address__isnull=False
|
|
660
|
-
)
|
|
666
|
+
)
|
|
661
667
|
)
|
|
662
|
-
|
|
663
|
-
for ip_address in update_ip_addresses:
|
|
668
|
+
for ip_address in ip_addresses:
|
|
664
669
|
try:
|
|
665
670
|
check_dns_records(ip_address, zone=self)
|
|
666
671
|
except ValidationError as exc:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from netaddr import IPNetwork
|
|
2
2
|
|
|
3
|
+
from django.conf import settings
|
|
3
4
|
from django.dispatch import receiver
|
|
4
5
|
from django.db.models.signals import pre_delete, pre_save, post_save, m2m_changed
|
|
5
6
|
from django.core.exceptions import ValidationError
|
|
@@ -17,21 +18,55 @@ from netbox_dns.utilities import (
|
|
|
17
18
|
delete_dns_records,
|
|
18
19
|
get_views_by_prefix,
|
|
19
20
|
get_ip_addresses_by_prefix,
|
|
20
|
-
get_ip_addresses_by_view,
|
|
21
21
|
)
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
DNSSYNC_CUSTOM_FIELDS = {
|
|
24
24
|
"ipaddress_dns_disabled": False,
|
|
25
25
|
"ipaddress_dns_record_ttl": None,
|
|
26
26
|
"ipaddress_dns_record_disable_ptr": False,
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
IPADDRESS_ACTIVE_STATUS = settings.PLUGINS_CONFIG["netbox_dns"][
|
|
30
|
+
"dnssync_ipaddress_active_status"
|
|
31
|
+
]
|
|
32
|
+
ENFORCE_UNIQUE_RECORDS = settings.PLUGINS_CONFIG["netbox_dns"]["enforce_unique_records"]
|
|
33
|
+
|
|
29
34
|
|
|
30
35
|
@receiver(post_clean, sender=IPAddress)
|
|
31
|
-
def
|
|
36
|
+
def ipam_dnssync_ipaddress_post_clean(instance, **kwargs):
|
|
32
37
|
if not isinstance(instance.address, IPNetwork):
|
|
33
38
|
return
|
|
34
39
|
|
|
40
|
+
if instance.custom_field_data.get("ipaddress_dns_disabled"):
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# +
|
|
44
|
+
# Check for uniqueness of IP address and dns_name. If unique records are
|
|
45
|
+
# enforced, report an error when trying to create the same IP address with
|
|
46
|
+
# the same dns_name. Ignore existing IP addresses that have their CF
|
|
47
|
+
# "ipaddress_dns_disabled" set to "True".
|
|
48
|
+
# -
|
|
49
|
+
duplicate_addresses = IPAddress.objects.filter(
|
|
50
|
+
address=instance.address,
|
|
51
|
+
vrf=instance.vrf,
|
|
52
|
+
dns_name=instance.dns_name,
|
|
53
|
+
status__in=IPADDRESS_ACTIVE_STATUS,
|
|
54
|
+
)
|
|
55
|
+
if instance.pk is not None:
|
|
56
|
+
duplicate_addresses = duplicate_addresses.exclude(pk=instance.pk)
|
|
57
|
+
|
|
58
|
+
if ENFORCE_UNIQUE_RECORDS and instance.status in IPADDRESS_ACTIVE_STATUS:
|
|
59
|
+
for ip_address in duplicate_addresses.only("custom_field_data"):
|
|
60
|
+
if not ip_address.custom_field_data.get("ipaddress_dns_disabled"):
|
|
61
|
+
raise ValidationError(
|
|
62
|
+
{
|
|
63
|
+
"dns_name": "Unique DNS records are enforced and there is already "
|
|
64
|
+
f"an active IP address {instance.address} with DNS name {instance.dns_name}. "
|
|
65
|
+
"Plesase choose a different name or disable record creation for this "
|
|
66
|
+
"IP address."
|
|
67
|
+
}
|
|
68
|
+
)
|
|
69
|
+
|
|
35
70
|
# +
|
|
36
71
|
# Check NetBox DNS record permission for changes to IPAddress custom fields
|
|
37
72
|
#
|
|
@@ -42,24 +77,26 @@ def ipam_autodns_ipaddress_post_clean(instance, **kwargs):
|
|
|
42
77
|
instance.pk is not None
|
|
43
78
|
and any(
|
|
44
79
|
(
|
|
45
|
-
cf_data.get(cf)
|
|
46
|
-
!= IPAddress.objects.get(pk=instance.pk).custom_field_data.get(
|
|
47
|
-
|
|
80
|
+
cf_data.get(cf, cf_default)
|
|
81
|
+
!= IPAddress.objects.get(pk=instance.pk).custom_field_data.get(
|
|
82
|
+
cf, cf_default
|
|
83
|
+
)
|
|
84
|
+
for cf, cf_default in DNSSYNC_CUSTOM_FIELDS.items()
|
|
48
85
|
)
|
|
49
86
|
)
|
|
50
|
-
and not check_record_permission(
|
|
87
|
+
and not check_record_permission()
|
|
51
88
|
) or (
|
|
52
89
|
instance.pk is None
|
|
53
90
|
and any(
|
|
54
91
|
(
|
|
55
|
-
cf_data.get(cf) != cf_default
|
|
56
|
-
for cf, cf_default in
|
|
92
|
+
cf_data.get(cf, cf_default) != cf_default
|
|
93
|
+
for cf, cf_default in DNSSYNC_CUSTOM_FIELDS.items()
|
|
57
94
|
)
|
|
58
95
|
)
|
|
59
|
-
and not check_record_permission(
|
|
96
|
+
and not check_record_permission(change=False, delete=False)
|
|
60
97
|
):
|
|
61
98
|
raise ValidationError(
|
|
62
|
-
f"User '{request.user}' is not allowed to alter
|
|
99
|
+
f"User '{request.user}' is not allowed to alter DNSsync custom fields"
|
|
63
100
|
)
|
|
64
101
|
|
|
65
102
|
try:
|
|
@@ -69,38 +106,51 @@ def ipam_autodns_ipaddress_post_clean(instance, **kwargs):
|
|
|
69
106
|
|
|
70
107
|
|
|
71
108
|
@receiver(pre_delete, sender=IPAddress)
|
|
72
|
-
def
|
|
109
|
+
def ipam_dnssync_ipaddress_pre_delete(instance, **kwargs):
|
|
73
110
|
delete_dns_records(instance)
|
|
74
111
|
|
|
75
112
|
|
|
76
113
|
@receiver(pre_save, sender=IPAddress)
|
|
77
|
-
def
|
|
114
|
+
def ipam_dnssync_ipaddress_pre_save(instance, **kwargs):
|
|
78
115
|
check_dns_records(instance)
|
|
79
116
|
|
|
80
117
|
|
|
81
118
|
@receiver(post_save, sender=IPAddress)
|
|
82
|
-
def
|
|
119
|
+
def ipam_dnssync_ipaddress_post_save(instance, **kwargs):
|
|
83
120
|
update_dns_records(instance)
|
|
84
121
|
|
|
85
122
|
|
|
86
123
|
@receiver(pre_save, sender=Prefix)
|
|
87
|
-
def
|
|
124
|
+
def ipam_dnssync_prefix_pre_save(instance, **kwargs):
|
|
88
125
|
"""
|
|
89
126
|
Changes that modify the prefix hierarchy cannot be validated properly before
|
|
90
|
-
commiting them. So the solution in this case is to
|
|
91
|
-
|
|
127
|
+
commiting them. So the solution in this case is to ask the user to deassign
|
|
128
|
+
the prefix from any views it is assigned to and retry.
|
|
92
129
|
"""
|
|
130
|
+
request = current_request.get()
|
|
131
|
+
|
|
93
132
|
if instance.pk is None or not instance.netbox_dns_views.exists():
|
|
94
133
|
return
|
|
95
134
|
|
|
96
|
-
saved_prefix = Prefix.objects.get(
|
|
135
|
+
saved_prefix = Prefix.objects.prefetch_related("netbox_dns_views").get(
|
|
136
|
+
pk=instance.pk
|
|
137
|
+
)
|
|
97
138
|
if saved_prefix.prefix != instance.prefix or saved_prefix.vrf != instance.vrf:
|
|
98
|
-
for view in
|
|
99
|
-
|
|
139
|
+
dns_views = ", ".join([view.name for view in instance.netbox_dns_views.all()])
|
|
140
|
+
if request is not None:
|
|
141
|
+
raise AbortRequest(
|
|
142
|
+
f"This prefix is currently assigned to the following DNS views: {dns_views}"
|
|
143
|
+
f"Please deassign it from these views before making changes to the prefix "
|
|
144
|
+
f"or VRF."
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
raise ValidationError(
|
|
148
|
+
f"Prefix is assigned to DNS views {dns_views}. Prefix and VRF must not be changed"
|
|
149
|
+
)
|
|
100
150
|
|
|
101
151
|
|
|
102
152
|
@receiver(pre_delete, sender=Prefix)
|
|
103
|
-
def
|
|
153
|
+
def ipam_dnssync_prefix_pre_delete(instance, **kwargs):
|
|
104
154
|
parent = instance.get_parents().last()
|
|
105
155
|
request = current_request.get()
|
|
106
156
|
|
|
@@ -144,7 +194,7 @@ def ipam_autodns_prefix_pre_delete(instance, **kwargs):
|
|
|
144
194
|
|
|
145
195
|
|
|
146
196
|
@receiver(m2m_changed, sender=_view.View.prefixes.through)
|
|
147
|
-
def
|
|
197
|
+
def ipam_dnssync_view_prefix_changed(**kwargs):
|
|
148
198
|
action = kwargs.get("action")
|
|
149
199
|
request = current_request.get()
|
|
150
200
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import django_tables2 as tables
|
|
2
|
+
|
|
3
|
+
from ipam.tables import PrefixTable
|
|
4
|
+
from utilities.tables import register_table_column
|
|
5
|
+
|
|
6
|
+
views = tables.ManyToManyColumn(
|
|
7
|
+
verbose_name="DNS Views",
|
|
8
|
+
linkify_item=True,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
register_table_column(views, "netbox_dns_views", PrefixTable)
|
netbox_dns/template_content.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from django.conf import settings
|
|
2
|
+
from django.urls import reverse
|
|
2
3
|
|
|
3
4
|
from netbox.plugins.utils import get_plugin_config
|
|
4
5
|
from netbox.plugins import PluginTemplateExtension
|
|
5
6
|
|
|
6
|
-
from netbox_dns.models import Record
|
|
7
|
+
from netbox_dns.models import Record
|
|
7
8
|
from netbox_dns.choices import RecordTypeChoices
|
|
8
9
|
from netbox_dns.tables import RelatedRecordTable, RelatedViewTable
|
|
9
10
|
from netbox_dns.utilities import get_views_by_prefix
|
|
@@ -63,6 +64,17 @@ class RelatedDNSViews(PluginTemplateExtension):
|
|
|
63
64
|
extra_context=context,
|
|
64
65
|
)
|
|
65
66
|
|
|
67
|
+
def buttons(self):
|
|
68
|
+
return self.render(
|
|
69
|
+
"netbox_dns/view/button.html",
|
|
70
|
+
extra_context={
|
|
71
|
+
"url": reverse(
|
|
72
|
+
"plugins:netbox_dns:prefix_views",
|
|
73
|
+
kwargs={"pk": self.context.get("object").pk},
|
|
74
|
+
),
|
|
75
|
+
},
|
|
76
|
+
)
|
|
77
|
+
|
|
66
78
|
|
|
67
79
|
class IPRelatedDNSRecords(PluginTemplateExtension):
|
|
68
80
|
model = "ipam.ipaddress"
|
|
@@ -102,7 +114,7 @@ class IPRelatedDNSRecords(PluginTemplateExtension):
|
|
|
102
114
|
)
|
|
103
115
|
|
|
104
116
|
|
|
105
|
-
if not settings.PLUGINS_CONFIG["netbox_dns"].get("
|
|
117
|
+
if not settings.PLUGINS_CONFIG["netbox_dns"].get("dnssync_disabled"):
|
|
106
118
|
template_extensions = [RelatedDNSRecords, RelatedDNSViews]
|
|
107
119
|
else:
|
|
108
120
|
template_extensions = []
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{% load perms %}
|
|
2
|
+
|
|
3
|
+
{% if perms.netbox_dns.change_view %}
|
|
4
|
+
<a href="{{ url }}?return_url={{ object.get_absolute_url }}">
|
|
5
|
+
<button type="submit" class="btn btn-primary" name="assign-view">
|
|
6
|
+
<i class="mdi mdi-eye-outline" aria-hidden="true"></i> DNS Views
|
|
7
|
+
</button>
|
|
8
|
+
</a>
|
|
9
|
+
{% endif %}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{% extends 'generic/_base.html' %}
|
|
2
|
+
|
|
3
|
+
{% block title %}
|
|
4
|
+
Configure DNS views for {{ object|meta:"verbose_name" }} {{ object }} {% if object.vrf %}[{{ object.vrf }}]{% endif %}
|
|
5
|
+
{% endblock title %}
|
|
6
|
+
|
|
7
|
+
{% block content %}
|
|
8
|
+
<div class="tab-pane show active" id="edit-form" role="tabpanel" aria-labelledby="object-list-tab">
|
|
9
|
+
|
|
10
|
+
<form action="" method="post" enctype="multipart/form-data" class="object-edit mt-5">
|
|
11
|
+
{% csrf_token %}
|
|
12
|
+
|
|
13
|
+
<div id="form_fields" hx-disinherit="hx-select hx-swap">
|
|
14
|
+
{% if inherited_from %}
|
|
15
|
+
<div class="card">
|
|
16
|
+
<table class="table table-hover attr-table">
|
|
17
|
+
<th>Views inherited from prefix {{ inherited_from }} {% if inherited_from.vrf %}[{{ inherited_from.vrf }}] {% endif %}</th>
|
|
18
|
+
{% for view in inherited_views %}
|
|
19
|
+
<tr><td>{{ view|linkify }}</td><td>{{ view.description }}</td></tr>
|
|
20
|
+
{% endfor %}
|
|
21
|
+
</table>
|
|
22
|
+
</div>
|
|
23
|
+
{% endif %}
|
|
24
|
+
{% block form %}
|
|
25
|
+
{% include 'htmx/form.html' %}
|
|
26
|
+
{% endblock form %}
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="text-end my-3">
|
|
30
|
+
{% block buttons %}
|
|
31
|
+
<a href="{{ return_url }}" class="btn btn-outline-secondary">Cancel</a>
|
|
32
|
+
<button type="submit" name="_update" class="btn btn-primary">Save</button>
|
|
33
|
+
{% endblock buttons %}
|
|
34
|
+
</div>
|
|
35
|
+
</form>
|
|
36
|
+
</div>
|
|
37
|
+
{% endblock content %}
|
|
38
|
+
|
|
39
|
+
{% block modals %}
|
|
40
|
+
{% include 'inc/htmx_modal.html' with size='lg' %}
|
|
41
|
+
{% endblock %}
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
</div>
|
|
36
36
|
<div class="col col-md-6">
|
|
37
37
|
{% include 'inc/panels/tags.html' %}
|
|
38
|
-
{% if not settings.PLUGINS_CONFIG.netbox_dns.
|
|
38
|
+
{% if not settings.PLUGINS_CONFIG.netbox_dns.dnssync_disabled %}
|
|
39
39
|
<div class="card">
|
|
40
40
|
<h5 class="card-header">
|
|
41
41
|
IPAM Prefixes
|
netbox_dns/urls/view.py
CHANGED
|
@@ -12,6 +12,7 @@ from netbox_dns.views import (
|
|
|
12
12
|
ViewBulkEditView,
|
|
13
13
|
ViewBulkDeleteView,
|
|
14
14
|
ViewZoneListView,
|
|
15
|
+
ViewPrefixEditView,
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
view_urlpatterns = [
|
|
@@ -36,4 +37,9 @@ view_urlpatterns = [
|
|
|
36
37
|
name="view_changelog",
|
|
37
38
|
kwargs={"model": View},
|
|
38
39
|
),
|
|
40
|
+
path(
|
|
41
|
+
"prefixes/<int:pk>/assign-views/",
|
|
42
|
+
ViewPrefixEditView.as_view(),
|
|
43
|
+
name="prefix_views",
|
|
44
|
+
),
|
|
39
45
|
]
|
netbox_dns/utilities/__init__.py
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
from .conversions import *
|
|
2
|
-
from .
|
|
2
|
+
from .ipam_dnssync import *
|
|
@@ -13,6 +13,7 @@ from ipam.models import IPAddress, Prefix
|
|
|
13
13
|
from netbox_dns.models import zone as _zone
|
|
14
14
|
from netbox_dns.models import record as _record
|
|
15
15
|
from netbox_dns.models import view as _view
|
|
16
|
+
from netbox_dns.choices import RecordStatusChoices
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
__all__ = (
|
|
@@ -46,13 +47,19 @@ def _get_record_status(ip_address):
|
|
|
46
47
|
RecordStatusChoices.STATE_ACTIVE
|
|
47
48
|
if ip_address.status
|
|
48
49
|
in settings.PLUGINS_CONFIG["netbox_dns"].get(
|
|
49
|
-
"
|
|
50
|
+
"dnssync_ipaddress_active_status", []
|
|
50
51
|
)
|
|
51
52
|
else RecordStatusChoices.STATUS_INACTIVE
|
|
52
53
|
)
|
|
53
54
|
|
|
54
55
|
|
|
55
|
-
def
|
|
56
|
+
def _valid_entry(ip_address, zone):
|
|
57
|
+
return zone.view in _get_assigned_views(ip_address) and dns_name.from_text(
|
|
58
|
+
ip_address.dns_name
|
|
59
|
+
).is_subdomain(dns_name.from_text(zone.name))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def get_zones(ip_address, view=None, old_zone=None):
|
|
56
63
|
if view is None:
|
|
57
64
|
views = _get_assigned_views(ip_address)
|
|
58
65
|
if not views:
|
|
@@ -72,10 +79,13 @@ def get_zones(ip_address, view=None):
|
|
|
72
79
|
active=True,
|
|
73
80
|
)
|
|
74
81
|
|
|
75
|
-
if not zones:
|
|
76
|
-
return []
|
|
77
|
-
|
|
78
82
|
zone_map = defaultdict(list)
|
|
83
|
+
|
|
84
|
+
if old_zone is not None:
|
|
85
|
+
zones = zones.exclude(pk=old_zone.pk)
|
|
86
|
+
if _valid_entry(ip_address, old_zone):
|
|
87
|
+
zone_map[old_zone.view].append(old_zone)
|
|
88
|
+
|
|
79
89
|
for zone in zones:
|
|
80
90
|
zone_map[zone.view].append(zone)
|
|
81
91
|
|
|
@@ -91,35 +101,49 @@ def check_dns_records(ip_address, zone=None, view=None):
|
|
|
91
101
|
|
|
92
102
|
if zone is None:
|
|
93
103
|
zones = get_zones(ip_address, view=view)
|
|
94
|
-
else:
|
|
95
|
-
zones = [zone]
|
|
96
|
-
|
|
97
|
-
if ip_address.pk is not None:
|
|
98
|
-
for record in ip_address.netbox_dns_records.filter(zone__in=zones):
|
|
99
|
-
if (
|
|
100
|
-
record.fqdn != ip_address.dns_name
|
|
101
|
-
or record.value != ip_address.address.ip
|
|
102
|
-
or record.status != _get_record_status(ip_address)
|
|
103
|
-
):
|
|
104
|
-
record.update_from_ip_address(ip_address)
|
|
105
104
|
|
|
106
|
-
|
|
107
|
-
|
|
105
|
+
if ip_address.pk is not None:
|
|
106
|
+
for record in ip_address.netbox_dns_records.filter(zone__in=zones):
|
|
107
|
+
if (
|
|
108
|
+
record.fqdn != ip_address.dns_name
|
|
109
|
+
or record.value != ip_address.address.ip
|
|
110
|
+
or record.status != _get_record_status(ip_address)
|
|
111
|
+
):
|
|
112
|
+
record.update_from_ip_address(ip_address)
|
|
113
|
+
|
|
114
|
+
if record is not None:
|
|
115
|
+
record.clean()
|
|
116
|
+
|
|
117
|
+
zones = _zone.Zone.objects.filter(
|
|
118
|
+
pk__in=[zone.pk for zone in zones]
|
|
119
|
+
).exclude(
|
|
120
|
+
pk__in=set(
|
|
121
|
+
ip_address.netbox_dns_records.all().values_list("zone", flat=True)
|
|
122
|
+
)
|
|
123
|
+
)
|
|
108
124
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
ip_address
|
|
125
|
+
for zone in zones:
|
|
126
|
+
record = _record.Record.create_from_ip_address(
|
|
127
|
+
ip_address,
|
|
128
|
+
zone,
|
|
112
129
|
)
|
|
113
|
-
)
|
|
114
130
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
131
|
+
if record is not None:
|
|
132
|
+
record.clean()
|
|
133
|
+
|
|
134
|
+
if ip_address.pk is None:
|
|
135
|
+
return
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
new_zone = get_zones(ip_address, old_zone=zone)[0]
|
|
139
|
+
except IndexError:
|
|
140
|
+
return
|
|
141
|
+
|
|
142
|
+
for record in ip_address.netbox_dns_records.filter(zone=zone):
|
|
143
|
+
record.update_from_ip_address(ip_address, new_zone)
|
|
120
144
|
|
|
121
145
|
if record is not None:
|
|
122
|
-
record.clean()
|
|
146
|
+
record.clean(new_zone=new_zone)
|
|
123
147
|
|
|
124
148
|
|
|
125
149
|
def update_dns_records(ip_address):
|
|
@@ -240,7 +264,7 @@ def get_ip_addresses_by_zone(zone):
|
|
|
240
264
|
|
|
241
265
|
|
|
242
266
|
def check_record_permission(add=True, change=True, delete=True):
|
|
243
|
-
checks = locals()
|
|
267
|
+
checks = locals().copy()
|
|
244
268
|
|
|
245
269
|
request = current_request.get()
|
|
246
270
|
|
|
@@ -249,8 +273,8 @@ def check_record_permission(add=True, change=True, delete=True):
|
|
|
249
273
|
|
|
250
274
|
return all(
|
|
251
275
|
(
|
|
252
|
-
request.user.has_perm(f"
|
|
253
|
-
for perm, check in
|
|
276
|
+
request.user.has_perm(f"netbox_dns.{perm}_record")
|
|
277
|
+
for perm, check in checks.items()
|
|
254
278
|
if check
|
|
255
279
|
)
|
|
256
280
|
)
|
netbox_dns/views/record.py
CHANGED
|
@@ -90,11 +90,11 @@ class RecordView(generic.ObjectView):
|
|
|
90
90
|
zone=parent_zone,
|
|
91
91
|
)
|
|
92
92
|
cname_records = cname_records.union(
|
|
93
|
-
|
|
93
|
+
{
|
|
94
94
|
record
|
|
95
95
|
for record in parent_cname_records
|
|
96
96
|
if record.value_fqdn == instance.fqdn
|
|
97
|
-
|
|
97
|
+
}
|
|
98
98
|
)
|
|
99
99
|
|
|
100
100
|
if cname_records:
|
netbox_dns/views/view.py
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
from utilities.views import ViewTab, register_model_view
|
|
2
2
|
|
|
3
3
|
from netbox.views import generic
|
|
4
|
+
from ipam.models import Prefix
|
|
4
5
|
|
|
5
6
|
from netbox_dns.models import View, Zone
|
|
6
7
|
from netbox_dns.filtersets import ViewFilterSet, ZoneFilterSet
|
|
7
|
-
from netbox_dns.forms import
|
|
8
|
+
from netbox_dns.forms import (
|
|
9
|
+
ViewForm,
|
|
10
|
+
ViewFilterForm,
|
|
11
|
+
ViewImportForm,
|
|
12
|
+
ViewBulkEditForm,
|
|
13
|
+
ViewPrefixEditForm,
|
|
14
|
+
)
|
|
8
15
|
from netbox_dns.tables import ViewTable, ZoneTable
|
|
16
|
+
from netbox_dns.utilities import get_views_by_prefix
|
|
9
17
|
|
|
10
18
|
|
|
11
19
|
__all__ = (
|
|
@@ -17,6 +25,7 @@ __all__ = (
|
|
|
17
25
|
"ViewBulkEditView",
|
|
18
26
|
"ViewBulkDeleteView",
|
|
19
27
|
"ViewZoneListView",
|
|
28
|
+
"ViewPrefixEditView",
|
|
20
29
|
)
|
|
21
30
|
|
|
22
31
|
|
|
@@ -61,6 +70,22 @@ class ViewBulkDeleteView(generic.BulkDeleteView):
|
|
|
61
70
|
table = ViewTable
|
|
62
71
|
|
|
63
72
|
|
|
73
|
+
class ViewPrefixEditView(generic.ObjectEditView):
|
|
74
|
+
queryset = Prefix.objects.all()
|
|
75
|
+
form = ViewPrefixEditForm
|
|
76
|
+
template_name = "netbox_dns/view/prefix.html"
|
|
77
|
+
|
|
78
|
+
def get_extra_context(self, request, instance):
|
|
79
|
+
parents = instance.get_parents()
|
|
80
|
+
if parents:
|
|
81
|
+
return {
|
|
82
|
+
"inherited_views": get_views_by_prefix(parents.last()),
|
|
83
|
+
"inherited_from": parents.filter(netbox_dns_views__isnull=False).last(),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return {}
|
|
87
|
+
|
|
88
|
+
|
|
64
89
|
@register_model_view(View, "zones")
|
|
65
90
|
class ViewZoneListView(generic.ObjectChildrenView):
|
|
66
91
|
queryset = View.objects.all().prefetch_related("zone_set")
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
netbox_dns/__init__.py,sha256=
|
|
1
|
+
netbox_dns/__init__.py,sha256=o6LleCsdMgaES4LaRbzvKSTH7jBS01AoIZNiKwxv97Y,1880
|
|
2
2
|
netbox_dns/api/nested_serializers.py,sha256=-ZhAiyf-8UHlkcBomBp1J7ci1dSwrxWRbbfskD-D_yQ,3172
|
|
3
3
|
netbox_dns/api/serializers.py,sha256=u-kQurUftGkUGAMh-VkMgXPebLYeZq9WDz9uKzkk2No,370
|
|
4
4
|
netbox_dns/api/serializers_/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -25,7 +25,7 @@ netbox_dns/fields/rfc2317.py,sha256=24qNNLbI-SGlsKqGaLNaVk8EHFrju65YTET3O-8XgTc,
|
|
|
25
25
|
netbox_dns/filtersets/__init__.py,sha256=zvHYWy23FFmK4vxLpoMo-OD5OQBtcTUV_HG-5LCtvQE,197
|
|
26
26
|
netbox_dns/filtersets/contact.py,sha256=VnlNX6dyUlEbj7tz9cgRKSWQOdg7OqP32cD2IyD5hu8,1091
|
|
27
27
|
netbox_dns/filtersets/nameserver.py,sha256=I7RoIUcgXyIoMrhuS0dJr8uPi0AdNj6D3G6t2oSiQ7s,1147
|
|
28
|
-
netbox_dns/filtersets/record.py,sha256=
|
|
28
|
+
netbox_dns/filtersets/record.py,sha256=Jt3RlI4MIfeY5xYkLse1y1rzmKCdmuk-CRAxxaNVYbY,3750
|
|
29
29
|
netbox_dns/filtersets/record_template.py,sha256=jGSjGFEnNSoxtUd7diV8wEhw8qZclz2dKLSqyVC7Djo,1548
|
|
30
30
|
netbox_dns/filtersets/registrar.py,sha256=Wh_l-IXRHnJhW7Pyokp3czQZISDKzXnWeSQKp512Drc,977
|
|
31
31
|
netbox_dns/filtersets/view.py,sha256=sGUhmyr66GY2CVXOFX2g5evDt0nbU6XPPCwptvnicHQ,993
|
|
@@ -37,16 +37,16 @@ netbox_dns/forms/nameserver.py,sha256=LHomCHmFcASobaD3Z7yhAyA24h-LrYImVMz-EUXbwK
|
|
|
37
37
|
netbox_dns/forms/record.py,sha256=svBVAFy-egDEPLcRWkxNi_1bkabKmWgJ87pmdNt6dh4,7155
|
|
38
38
|
netbox_dns/forms/record_template.py,sha256=Q77p9sExJ8Xbl-Co2Px2R0At5O3naQJwx4pnino6i2o,5573
|
|
39
39
|
netbox_dns/forms/registrar.py,sha256=FMnvrcq62R3wNp_2ZUEk3v_PIav0KrWPATaJ7_9KFAo,3758
|
|
40
|
-
netbox_dns/forms/view.py,sha256=
|
|
41
|
-
netbox_dns/forms/zone.py,sha256
|
|
40
|
+
netbox_dns/forms/view.py,sha256=DkfZOLkCClTF-7pLMiZE8J1Z9_oxmeBiEWZ7-_yTZro,9045
|
|
41
|
+
netbox_dns/forms/zone.py,sha256=-ww_4nepCwVnsCsJdowtzz2HPmKLqNVedHUi5QC8uoM,23445
|
|
42
42
|
netbox_dns/forms/zone_template.py,sha256=UNykid5pRB_ydy40j2DzRlBXp3_QAOqdqxdUojKYTd4,8161
|
|
43
43
|
netbox_dns/graphql/__init__.py,sha256=ZZSsx-VM108tB_FrcVy3uGGhtmePpkXnY5U1ytnoTvE,490
|
|
44
44
|
netbox_dns/graphql/filters.py,sha256=6Ot_d1e7h5lVXVVBB3hyWUql94K3zsK9Tjb3RVJqluw,1706
|
|
45
45
|
netbox_dns/graphql/schema.py,sha256=P-oQ8ei3sC6XLhgCa_riRbRTrMkPCVTJXkGv0U2rPYw,2682
|
|
46
|
-
netbox_dns/graphql/types.py,sha256=
|
|
46
|
+
netbox_dns/graphql/types.py,sha256=Obr9JDsMJlMaJLKbTau7prZUYI6pQj6aIpT-nT4JnZs,6210
|
|
47
47
|
netbox_dns/management/commands/cleanup_database.py,sha256=kfnyybudwKGigjJmrOwafPWSUasZr9jQsxN4eWAgMvY,5969
|
|
48
48
|
netbox_dns/management/commands/cleanup_rrset_ttl.py,sha256=UFRURLBcFeGHUS2lrYFv7UWIebjI72aG1EUQJt0XsXw,2046
|
|
49
|
-
netbox_dns/management/commands/
|
|
49
|
+
netbox_dns/management/commands/setup_dnssync.py,sha256=qtVj6egSjclaQbuI60hLfl-zg89VJVbX-TB17f1k77Y,5730
|
|
50
50
|
netbox_dns/management/commands/update_soa.py,sha256=Rj_Xk-qpwkAVRubVnM5OqSTwgzi93E0PqjwGb3rYjf0,660
|
|
51
51
|
netbox_dns/migrations/0001_squashed_netbox_dns_0_15.py,sha256=3U0810NWSHPu2dTSHpfzlleDgwMS04FhJ_CkO76SDaw,10283
|
|
52
52
|
netbox_dns/migrations/0001_squashed_netbox_dns_0_22.py,sha256=ML6Hp17lrXiaG0eUlBjKMm6HUNhw0AHPnKrb9AN-F6E,20279
|
|
@@ -72,17 +72,18 @@ netbox_dns/mixins/object_modification.py,sha256=JbGi8a52wkZ3fFBlfat590CfqRJcEWxB
|
|
|
72
72
|
netbox_dns/models/__init__.py,sha256=wjwNsRttUVYQHZODZi806a_iUDoq_o7mdKObqh1N7N4,300
|
|
73
73
|
netbox_dns/models/contact.py,sha256=oNLyD_6TOTNQQTcCvv6TAC7OkzPTMIRy2NP5nwNKaNg,3009
|
|
74
74
|
netbox_dns/models/nameserver.py,sha256=yKo4Fwqnv5VtTndU2px7tRS3voF3Cal7OWQ6AImLwl0,3208
|
|
75
|
-
netbox_dns/models/record.py,sha256=
|
|
75
|
+
netbox_dns/models/record.py,sha256=LgJJvL85hKrN9a3fpq90k9oit8oqNvzwbrA-MVve8DE,25963
|
|
76
76
|
netbox_dns/models/record_template.py,sha256=3t9VceviX3kNIo5o0VPVFupLFDqPxpHIVLp5U3pBKB4,4661
|
|
77
77
|
netbox_dns/models/registrar.py,sha256=T_oMUlTWTDixOVlIbEZGvOBdvUrKxRkkS41xgM2Oee8,1557
|
|
78
78
|
netbox_dns/models/view.py,sha256=SYmhNYyRCv0rSCK5jrHtug4QgfWCBbjsAjZEEHk02QU,2873
|
|
79
|
-
netbox_dns/models/zone.py,sha256=
|
|
79
|
+
netbox_dns/models/zone.py,sha256=C1f6uGKGeD_FKtFhWXiUO7gKF19pzu-9-pj0txP8R1E,29063
|
|
80
80
|
netbox_dns/models/zone_template.py,sha256=lkiSIfx8KM0Cs3Mb3dLBxKbSpcssVUzQiSmD5W46was,3753
|
|
81
81
|
netbox_dns/navigation.py,sha256=EITDZkbpu4KCC9u4Noj7OORWnkL3EYT2RIRvYlTw34Q,5961
|
|
82
82
|
netbox_dns/signals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
83
|
-
netbox_dns/signals/
|
|
83
|
+
netbox_dns/signals/ipam_dnssync.py,sha256=uGK8R_pEKZJ4STTYgw2y5j0g6T0pQZbI9HZLmtfdOwM,7878
|
|
84
84
|
netbox_dns/tables/__init__.py,sha256=s41w4o77tIwmhnLjsOsg08R9m3wrlomkkfCLTVQuPzc,196
|
|
85
85
|
netbox_dns/tables/contact.py,sha256=sPs7d1ZhVC5dOS37dPYFqebNd7WGvsV_eYzX_TMcbzY,804
|
|
86
|
+
netbox_dns/tables/ipam_dnssync.py,sha256=7gxf6nXn2zgpnpCr5E5qebXv7QCzrgIqfSzyka53USU,272
|
|
86
87
|
netbox_dns/tables/nameserver.py,sha256=fFiE-yH-_GyRDaV4SVw094r6uH58Kx56NSWDGaMR58g,764
|
|
87
88
|
netbox_dns/tables/record.py,sha256=0Yg0qwZ8Vjz6pkZnmof4ZK1Hsvk9DNEzmJwoIwJZJFQ,3189
|
|
88
89
|
netbox_dns/tables/record_template.py,sha256=16Lu-WDjs2m9Uxx6WkURL39NlyJ8lWzj5Kl6C6lz3E8,2159
|
|
@@ -90,7 +91,7 @@ netbox_dns/tables/registrar.py,sha256=M-ckyQUs6dqjTCPf7bAr6UuLEA-q9f9CxKW7yp3rGo
|
|
|
90
91
|
netbox_dns/tables/view.py,sha256=jf2S4TiOdMq6-wWk0ndR1uBJpkOx_f3pqAuM1nSXTBo,1178
|
|
91
92
|
netbox_dns/tables/zone.py,sha256=IeCiflrQBn1INV_PxoTySWQrDalykY4mDSG76VXC5WM,1877
|
|
92
93
|
netbox_dns/tables/zone_template.py,sha256=70hvS-xpeaLkcM6y0R9xsUMQVKgTgZJaWWNd99BfmzI,1479
|
|
93
|
-
netbox_dns/template_content.py,sha256=
|
|
94
|
+
netbox_dns/template_content.py,sha256=YzE-ZJlERhFybrUKJrmNwHk8_8RPNexkV66x62Sbzic,3718
|
|
94
95
|
netbox_dns/templates/netbox_dns/contact.html,sha256=fMHAQyLXIxohKoCTxFEnKetl9UVXeQgjasfpv_JONaw,2855
|
|
95
96
|
netbox_dns/templates/netbox_dns/nameserver.html,sha256=DpTdetQVV_jKThDbi62LvbhiCay-1QxR-yiJEiPFm4w,1554
|
|
96
97
|
netbox_dns/templates/netbox_dns/record/managed.html,sha256=G6LPG1koUGuzUiwYdv1okdVa4sKaofiQegDBnsFL0kA,89
|
|
@@ -98,8 +99,10 @@ netbox_dns/templates/netbox_dns/record/related.html,sha256=Aqor8uGcuHQTHjlX-Xmni
|
|
|
98
99
|
netbox_dns/templates/netbox_dns/record.html,sha256=o3z_D6Fqqn7nx1IwPXKQ75ZaPhU6kae0WpaWa3UMcxQ,5211
|
|
99
100
|
netbox_dns/templates/netbox_dns/recordtemplate.html,sha256=9tkXtKqa5p3LdOU9REm99WSFwGJaH8OczpIqXZuXMcg,3099
|
|
100
101
|
netbox_dns/templates/netbox_dns/registrar.html,sha256=O5veGmW59Pf5yN25ihPLvRIkA2P7xmSGv0G3NrRG8vI,2152
|
|
102
|
+
netbox_dns/templates/netbox_dns/view/button.html,sha256=oXKNyPtY8XIu2sxtZWpFRXKXv862407ESyUQ4YsWCGE,292
|
|
103
|
+
netbox_dns/templates/netbox_dns/view/prefix.html,sha256=HD8f4mnbzFOXDj3Y_yq8yEeDpz_yFud8ZMpqbxzCEnA,1445
|
|
101
104
|
netbox_dns/templates/netbox_dns/view/related.html,sha256=W9Ie2aOsFkWyYtBnZn38seQDBmyJkV9dqFDG-Dq3yMk,736
|
|
102
|
-
netbox_dns/templates/netbox_dns/view.html,sha256=
|
|
105
|
+
netbox_dns/templates/netbox_dns/view.html,sha256=zqf42FGdNudoIIWCaCe9bmP_VOZDbv3GjS-qJNMKPVY,2479
|
|
103
106
|
netbox_dns/templates/netbox_dns/zone/base.html,sha256=n_E4aVYdGeZZl-ARE8sb4DgAAgPs92X1UEFepX3xIlM,495
|
|
104
107
|
netbox_dns/templates/netbox_dns/zone/child.html,sha256=kH56PJFBGCjiRdIh7zCtClnZdfOChqN_sYslsyoz5gU,2147
|
|
105
108
|
netbox_dns/templates/netbox_dns/zone/child_zone.html,sha256=b9CSGWEfWT7hLQ80gApMnu7mXM8w2LT-3UaOYe6HIRQ,510
|
|
@@ -115,12 +118,12 @@ netbox_dns/urls/nameserver.py,sha256=BBbY-wqPqCquvLLv1_JhqToj7oDHhPNGCWHt0IfjBNM
|
|
|
115
118
|
netbox_dns/urls/record.py,sha256=bDprohTso1N0GtPXH4X3TNHnkxopiOSQFXWItifEZ_k,1432
|
|
116
119
|
netbox_dns/urls/record_template.py,sha256=Z-7aA-rPIxRBCmXNUiQcHIgjYfai28Tf_sLtkl2ihDk,1827
|
|
117
120
|
netbox_dns/urls/registrar.py,sha256=u6B0zGGYNUJIKTo9uGiUeZLPD0QMGaQOAPShGEy4NaA,1728
|
|
118
|
-
netbox_dns/urls/view.py,sha256=
|
|
121
|
+
netbox_dns/urls/view.py,sha256=jz5ANOOLCMAcWermTZYGq9BvnP02jpKGL6hCm33C47Q,1478
|
|
119
122
|
netbox_dns/urls/zone.py,sha256=rmB1BkzmWNG06ILUf-39Aj6-SBFkwQouyixMQiamqPc,2005
|
|
120
123
|
netbox_dns/urls/zone_template.py,sha256=w3Gu8qfLCWyHofeLkGZd1HpYSlcslomVlBQJZyqh8kk,1690
|
|
121
|
-
netbox_dns/utilities/__init__.py,sha256=
|
|
124
|
+
netbox_dns/utilities/__init__.py,sha256=mmR0JdH1DJVhUKesnO3CZFj0Rw_wrsMPoYTpOOKHl9I,55
|
|
122
125
|
netbox_dns/utilities/conversions.py,sha256=NS37SoMqXc13wNWRkKnLfyQbVi6QKD33fu5ovTKRo74,1979
|
|
123
|
-
netbox_dns/utilities/
|
|
126
|
+
netbox_dns/utilities/ipam_dnssync.py,sha256=LIUCigDdobPikneY-td3ydY2iK-iDDe75VGxcCBLdP8,8188
|
|
124
127
|
netbox_dns/validators/__init__.py,sha256=Mr8TvmcJTa8Pubj8TzbFBKfbHhEmGcr5JdQvczEJ39A,72
|
|
125
128
|
netbox_dns/validators/dns_name.py,sha256=B4A0BOW5pKDjjukvksriRtnLzkYTx_pFjh7eqKo6PBE,3069
|
|
126
129
|
netbox_dns/validators/dns_value.py,sha256=y2Zga4hmywqDrTBXcMC-sWaFbw4eoY8pySq7cWnMP8Y,2822
|
|
@@ -128,13 +131,13 @@ netbox_dns/validators/rfc2317.py,sha256=ivylEiNKlmX2x41rwqDrFkD5CFf9FtpNEfsKHX_p
|
|
|
128
131
|
netbox_dns/views/__init__.py,sha256=s41w4o77tIwmhnLjsOsg08R9m3wrlomkkfCLTVQuPzc,196
|
|
129
132
|
netbox_dns/views/contact.py,sha256=qM9F6MQBvO8ERR7quGLdQ5kW4roNLJ61As8m0qQTapg,2471
|
|
130
133
|
netbox_dns/views/nameserver.py,sha256=DFr0eybMshc1FW06g4cy9Nk4VRMxRqakI5KtHFiAVRc,3286
|
|
131
|
-
netbox_dns/views/record.py,sha256=
|
|
134
|
+
netbox_dns/views/record.py,sha256=Qv-Yf9CxlHz9C9-8hy-_WVbRL5f6opXUHJD4SsDcVGo,4587
|
|
132
135
|
netbox_dns/views/record_template.py,sha256=BkemTBEramLhYqB6HrA80sNgtduW1ZOJwbYs3i7srik,2510
|
|
133
136
|
netbox_dns/views/registrar.py,sha256=yRQgFm3vgBD21ZQex9asjs0QWegvSHlcyHXLnjvc5xs,2324
|
|
134
|
-
netbox_dns/views/view.py,sha256=
|
|
137
|
+
netbox_dns/views/view.py,sha256=iXBJTc3JD5cD5z0RTcHVTtYV-KNIJGneeoxymXChdUE,2759
|
|
135
138
|
netbox_dns/views/zone.py,sha256=SKhf_WHcFVpKqFTuUMf-Dmxu1AwFHBeo_DtD8UGFrJ8,5483
|
|
136
139
|
netbox_dns/views/zone_template.py,sha256=qvXl-bpc1fMc1WFngynj4-Q3-JJDgKdT-r54s4M1D0s,2118
|
|
137
|
-
netbox_plugin_dns-1.1.
|
|
138
|
-
netbox_plugin_dns-1.1.
|
|
139
|
-
netbox_plugin_dns-1.1.
|
|
140
|
-
netbox_plugin_dns-1.1.
|
|
140
|
+
netbox_plugin_dns-1.1.0b4.dist-info/LICENSE,sha256=I3tDu11bZfhFm3EkV4zOD5TmWgLjnUNLEFwrdjniZYs,1112
|
|
141
|
+
netbox_plugin_dns-1.1.0b4.dist-info/METADATA,sha256=ktOXrD3WonLsUqWDbTg_n8w_ItTFfW7NB_60nuMcnwY,6406
|
|
142
|
+
netbox_plugin_dns-1.1.0b4.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
143
|
+
netbox_plugin_dns-1.1.0b4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|