netbox-plugin-dns 1.1.0b1__py3-none-any.whl → 1.1b3__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 +3 -2
- netbox_dns/forms/view.py +93 -4
- netbox_dns/forms/zone.py +8 -0
- netbox_dns/management/commands/setup_autodns.py +40 -20
- netbox_dns/models/record.py +54 -11
- netbox_dns/models/zone.py +19 -0
- netbox_dns/signals/ipam_autodns.py +99 -16
- netbox_dns/template_content.py +12 -0
- 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 +2 -6
- netbox_dns/urls/view.py +6 -0
- netbox_dns/utilities/ipam_autodns.py +94 -20
- netbox_dns/views/view.py +26 -1
- {netbox_plugin_dns-1.1.0b1.dist-info → netbox_plugin_dns-1.1b3.dist-info}/METADATA +2 -2
- {netbox_plugin_dns-1.1.0b1.dist-info → netbox_plugin_dns-1.1b3.dist-info}/RECORD +18 -16
- {netbox_plugin_dns-1.1.0b1.dist-info → netbox_plugin_dns-1.1b3.dist-info}/LICENSE +0 -0
- {netbox_plugin_dns-1.1.0b1.dist-info → netbox_plugin_dns-1.1b3.dist-info}/WHEEL +0 -0
netbox_dns/__init__.py
CHANGED
|
@@ -5,14 +5,14 @@ from ipam.choices import IPAddressStatusChoices
|
|
|
5
5
|
|
|
6
6
|
from netbox_dns.choices import RecordTypeChoices
|
|
7
7
|
|
|
8
|
-
__version__ = "1.
|
|
8
|
+
__version__ = "1.1b3"
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class DNSConfig(PluginConfig):
|
|
12
12
|
name = "netbox_dns"
|
|
13
13
|
verbose_name = "NetBox DNS"
|
|
14
14
|
description = "NetBox plugin for DNS data"
|
|
15
|
-
min_version = "4.
|
|
15
|
+
min_version = "4.0.0"
|
|
16
16
|
version = __version__
|
|
17
17
|
author = "Peter Eckel"
|
|
18
18
|
author_email = "pete@netbox-dns.org"
|
|
@@ -31,6 +31,7 @@ class DNSConfig(PluginConfig):
|
|
|
31
31
|
IPAddressStatusChoices.STATUS_DHCP,
|
|
32
32
|
IPAddressStatusChoices.STATUS_SLAAC,
|
|
33
33
|
],
|
|
34
|
+
"autodns_conflict_deactivate": False,
|
|
34
35
|
"tolerate_characters_in_zone_labels": "",
|
|
35
36
|
"tolerate_underscores_in_labels": False,
|
|
36
37
|
"tolerate_underscores_in_hostnames": False, # Deprecated, will be removed in 1.2.0
|
netbox_dns/forms/view.py
CHANGED
|
@@ -21,12 +21,14 @@ 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
30
|
update_dns_records,
|
|
31
|
+
get_ip_addresses_by_prefix,
|
|
30
32
|
get_views_by_prefix,
|
|
31
33
|
)
|
|
32
34
|
|
|
@@ -36,6 +38,7 @@ __all__ = (
|
|
|
36
38
|
"ViewFilterForm",
|
|
37
39
|
"ViewImportForm",
|
|
38
40
|
"ViewBulkEditForm",
|
|
41
|
+
"ViewPrefixEditForm",
|
|
39
42
|
)
|
|
40
43
|
|
|
41
44
|
|
|
@@ -52,7 +55,7 @@ class ViewPrefixUpdateMixin:
|
|
|
52
55
|
for prefix in prefixes.difference(old_prefixes):
|
|
53
56
|
for ip_address in get_ip_addresses_by_prefix(prefix, check_view=False):
|
|
54
57
|
try:
|
|
55
|
-
|
|
58
|
+
check_dns_records(ip_address, view=self.instance)
|
|
56
59
|
except ValidationError as exc:
|
|
57
60
|
self.add_error("prefixes", exc.messages)
|
|
58
61
|
|
|
@@ -74,7 +77,7 @@ class ViewPrefixUpdateMixin:
|
|
|
74
77
|
# parent. If that's the case, the IP addresses need to be checked.
|
|
75
78
|
# -
|
|
76
79
|
if (parent := check_prefix.get_parents().last()) is None:
|
|
77
|
-
|
|
80
|
+
continue
|
|
78
81
|
|
|
79
82
|
for view in get_views_by_prefix(parent):
|
|
80
83
|
if view == self.instance:
|
|
@@ -84,7 +87,7 @@ class ViewPrefixUpdateMixin:
|
|
|
84
87
|
check_prefix, check_view=False
|
|
85
88
|
):
|
|
86
89
|
try:
|
|
87
|
-
|
|
90
|
+
check_dns_records(ip_address, view=view)
|
|
88
91
|
except ValidationError as exc:
|
|
89
92
|
self.add_error("prefixes", exc.messages)
|
|
90
93
|
|
|
@@ -96,6 +99,21 @@ class ViewForm(ViewPrefixUpdateMixin, TenancyForm, NetBoxModelForm):
|
|
|
96
99
|
if settings.PLUGINS_CONFIG["netbox_dns"].get("autodns_disabled"):
|
|
97
100
|
del self.fields["prefixes"]
|
|
98
101
|
|
|
102
|
+
if request := current_request.get():
|
|
103
|
+
if not request.user.has_perm("ipam.view_prefix"):
|
|
104
|
+
self._saved_prefixes = self.initial["prefixes"]
|
|
105
|
+
self.initial["prefixes"] = []
|
|
106
|
+
self.fields["prefixes"].disabled = True
|
|
107
|
+
self.fields["prefixes"].widget.attrs[
|
|
108
|
+
"placeholder"
|
|
109
|
+
] = "You do not have permission to modify assigned prefixes"
|
|
110
|
+
|
|
111
|
+
def clean_prefixes(self):
|
|
112
|
+
if hasattr(self, "_saved_prefixes"):
|
|
113
|
+
return self._saved_prefixes
|
|
114
|
+
|
|
115
|
+
return self.cleaned_data["prefixes"]
|
|
116
|
+
|
|
99
117
|
prefixes = PrefixDynamicModelMultipleChoiceField(
|
|
100
118
|
queryset=Prefix.objects.all(),
|
|
101
119
|
required=False,
|
|
@@ -200,3 +218,74 @@ class ViewBulkEditForm(NetBoxModelBulkEditForm):
|
|
|
200
218
|
)
|
|
201
219
|
|
|
202
220
|
nullable_fields = ("description", "tenant")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class ViewPrefixEditForm(forms.ModelForm):
|
|
224
|
+
views = DynamicModelMultipleChoiceField(
|
|
225
|
+
queryset=View.objects.all(),
|
|
226
|
+
required=False,
|
|
227
|
+
label="Assigned DNS Views",
|
|
228
|
+
help_text="Explicitly assigning DNS views overrides all inherited views for this prefix",
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
class Meta:
|
|
232
|
+
model = Prefix
|
|
233
|
+
fields = ("views",)
|
|
234
|
+
|
|
235
|
+
def __init__(self, *args, **kwargs):
|
|
236
|
+
super().__init__(*args, **kwargs)
|
|
237
|
+
|
|
238
|
+
self.initial["views"] = self.instance.netbox_dns_views.all()
|
|
239
|
+
self._permission_denied = False
|
|
240
|
+
|
|
241
|
+
if request := current_request.get():
|
|
242
|
+
if not request.user.has_perm("netbox_dns.change_view"):
|
|
243
|
+
self._permission_denied = True
|
|
244
|
+
self.initial["views"] = []
|
|
245
|
+
self.fields["views"].disabled = True
|
|
246
|
+
self.fields["views"].widget.attrs[
|
|
247
|
+
"placeholder"
|
|
248
|
+
] = "You do not have permission to modify assigned views"
|
|
249
|
+
|
|
250
|
+
def clean(self, *args, **kwargs):
|
|
251
|
+
if self._permission_denied:
|
|
252
|
+
return
|
|
253
|
+
|
|
254
|
+
prefix = self.instance
|
|
255
|
+
|
|
256
|
+
super().clean(*args, **kwargs)
|
|
257
|
+
|
|
258
|
+
views = self.cleaned_data.get("views")
|
|
259
|
+
old_views = prefix.netbox_dns_views.all()
|
|
260
|
+
|
|
261
|
+
check_views = View.objects.none()
|
|
262
|
+
|
|
263
|
+
if not views.exists():
|
|
264
|
+
if (parent := prefix.get_parents().last()) is not None:
|
|
265
|
+
check_views = parent.netbox_dns_views.all().difference(old_views)
|
|
266
|
+
|
|
267
|
+
else:
|
|
268
|
+
check_views = views.difference(old_views)
|
|
269
|
+
|
|
270
|
+
for view in check_views:
|
|
271
|
+
try:
|
|
272
|
+
for ip_address in get_ip_addresses_by_prefix(prefix, check_view=False):
|
|
273
|
+
check_dns_records(ip_address, view=view)
|
|
274
|
+
except ValidationError as exc:
|
|
275
|
+
self.add_error("views", exc.messages)
|
|
276
|
+
|
|
277
|
+
def save(self, *args, **kwargs):
|
|
278
|
+
prefix = self.instance
|
|
279
|
+
|
|
280
|
+
if self._permission_denied:
|
|
281
|
+
return prefix
|
|
282
|
+
|
|
283
|
+
old_views = prefix.netbox_dns_views.all()
|
|
284
|
+
views = self.cleaned_data.get("views")
|
|
285
|
+
|
|
286
|
+
for view in views.difference(old_views):
|
|
287
|
+
view.prefixes.add(prefix)
|
|
288
|
+
for view in old_views.difference(views):
|
|
289
|
+
view.prefixes.remove(prefix)
|
|
290
|
+
|
|
291
|
+
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 = dict()
|
|
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)
|
|
@@ -29,8 +29,8 @@ class Command(BaseCommand):
|
|
|
29
29
|
CustomField.objects.get(
|
|
30
30
|
name=cf, object_types=ipaddress_object_type
|
|
31
31
|
).delete()
|
|
32
|
-
if options.get("verbosity")
|
|
33
|
-
self.stdout.write(f"
|
|
32
|
+
if options.get("verbosity"):
|
|
33
|
+
self.stdout.write(f"Removed custom field '{cf}'")
|
|
34
34
|
except CustomField.DoesNotExist:
|
|
35
35
|
pass
|
|
36
36
|
return
|
|
@@ -38,7 +38,7 @@ class Command(BaseCommand):
|
|
|
38
38
|
# +
|
|
39
39
|
# Remove pre-existing IPAM Coupling custom fields
|
|
40
40
|
# -
|
|
41
|
-
if options.get("verbosity"):
|
|
41
|
+
if options.get("verbosity") >= 2:
|
|
42
42
|
self.stdout.write(f"Trying to remove obsolete IPAM Coupling custom fields")
|
|
43
43
|
for cf in (
|
|
44
44
|
"ipaddress_dns_record_name",
|
|
@@ -48,12 +48,12 @@ class Command(BaseCommand):
|
|
|
48
48
|
CustomField.objects.get(
|
|
49
49
|
name=cf, object_types=ipaddress_object_type
|
|
50
50
|
).delete()
|
|
51
|
-
if options.get("verbosity")
|
|
51
|
+
if options.get("verbosity"):
|
|
52
52
|
self.stdout.write(f"Removed custom field '{cf}'")
|
|
53
53
|
except CustomField.DoesNotExist:
|
|
54
54
|
pass
|
|
55
55
|
|
|
56
|
-
if options.get("verbosity"):
|
|
56
|
+
if options.get("verbosity") >= 2:
|
|
57
57
|
self.stdout.write(f"Creating IPAM AutoDNS custom fields")
|
|
58
58
|
|
|
59
59
|
if not CustomField.objects.filter(
|
|
@@ -61,7 +61,7 @@ class Command(BaseCommand):
|
|
|
61
61
|
type=CustomFieldTypeChoices.TYPE_BOOLEAN,
|
|
62
62
|
object_types=ipaddress_object_type,
|
|
63
63
|
).exists():
|
|
64
|
-
|
|
64
|
+
cf_autodns_disabled = CustomField.objects.create(
|
|
65
65
|
name="ipaddress_dns_disabled",
|
|
66
66
|
label="Disable AutoDNS",
|
|
67
67
|
description="Disable DNS address and pointer record generation for this address",
|
|
@@ -72,15 +72,23 @@ class Command(BaseCommand):
|
|
|
72
72
|
is_cloneable=True,
|
|
73
73
|
weight=100,
|
|
74
74
|
)
|
|
75
|
-
|
|
76
|
-
if options.get("verbosity")
|
|
75
|
+
cf_autodns_disabled.object_types.set([ipaddress_object_type])
|
|
76
|
+
if options.get("verbosity"):
|
|
77
77
|
self.stdout.write("Created custom field 'ipaddress_dns_disabled'")
|
|
78
78
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
79
|
+
try:
|
|
80
|
+
cf_ttl = CustomField.objects.get(
|
|
81
|
+
name="ipaddress_dns_record_ttl",
|
|
82
|
+
type=CustomFieldTypeChoices.TYPE_INTEGER,
|
|
83
|
+
object_types=ipaddress_object_type,
|
|
84
|
+
)
|
|
85
|
+
if cf_ttl.group_name != "AutoDNS":
|
|
86
|
+
cf_ttl.group_name = "AutoDNS"
|
|
87
|
+
cf_ttl.description = ("TTL for DNS records created for this address",)
|
|
88
|
+
cf_ttl.save()
|
|
89
|
+
if options.get("verbosity"):
|
|
90
|
+
self.stdout.write("Updated custom field 'ipaddress_dns_record_ttl'")
|
|
91
|
+
except CustomField.DoesNotExist:
|
|
84
92
|
cf_ttl = CustomField.objects.create(
|
|
85
93
|
name="ipaddress_dns_record_ttl",
|
|
86
94
|
description="TTL for DNS records created for this address",
|
|
@@ -94,14 +102,26 @@ class Command(BaseCommand):
|
|
|
94
102
|
weight=200,
|
|
95
103
|
)
|
|
96
104
|
cf_ttl.object_types.set([ipaddress_object_type])
|
|
97
|
-
if options.get("verbosity")
|
|
105
|
+
if options.get("verbosity"):
|
|
98
106
|
self.stdout.write("Created custom field 'ipaddress_dns_record_ttl'")
|
|
99
107
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
try:
|
|
109
|
+
cf_disable_ptr = CustomField.objects.get(
|
|
110
|
+
name="ipaddress_dns_record_disable_ptr",
|
|
111
|
+
type=CustomFieldTypeChoices.TYPE_BOOLEAN,
|
|
112
|
+
object_types=ipaddress_object_type,
|
|
113
|
+
)
|
|
114
|
+
if cf_disable_ptr.group_name != "AutoDNS":
|
|
115
|
+
cf_disable_ptr.group_name = "AutoDNS"
|
|
116
|
+
cf_disable_ptr.description = (
|
|
117
|
+
"Disable DNS PTR record generation for this address",
|
|
118
|
+
)
|
|
119
|
+
cf_disable_ptr.save()
|
|
120
|
+
if options.get("verbosity"):
|
|
121
|
+
self.stdout.write(
|
|
122
|
+
"Updated custom field 'ipaddress_dns_record_disable_ptr'"
|
|
123
|
+
)
|
|
124
|
+
except CustomField.DoesNotExist:
|
|
105
125
|
cf_disable_ptr = CustomField.objects.create(
|
|
106
126
|
name="ipaddress_dns_record_disable_ptr",
|
|
107
127
|
description="Disable DNS PTR record generation for this address",
|
|
@@ -114,7 +134,7 @@ class Command(BaseCommand):
|
|
|
114
134
|
weight=300,
|
|
115
135
|
)
|
|
116
136
|
cf_disable_ptr.object_types.set([ipaddress_object_type])
|
|
117
|
-
if options.get("verbosity")
|
|
137
|
+
if options.get("verbosity"):
|
|
118
138
|
self.stdout.write(
|
|
119
139
|
"Created custom field 'ipaddress_dns_record_disable_ptr'"
|
|
120
140
|
)
|
netbox_dns/models/record.py
CHANGED
|
@@ -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,
|
|
@@ -562,13 +571,41 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
562
571
|
if self.pk is not None:
|
|
563
572
|
records = records.exclude(pk=self.pk)
|
|
564
573
|
|
|
565
|
-
if
|
|
574
|
+
if records.exists():
|
|
575
|
+
if self.ipam_ip_address is not None:
|
|
576
|
+
if not records.filter(
|
|
577
|
+
ipam_ip_address__isnull=True
|
|
578
|
+
).exists() or get_plugin_config(
|
|
579
|
+
"netbox_dns", "autodns_conflict_deactivate", False
|
|
580
|
+
):
|
|
581
|
+
return
|
|
582
|
+
|
|
566
583
|
raise ValidationError(
|
|
567
584
|
{
|
|
568
585
|
"value": f"There is already an active {self.type} record for name {self.name} in zone {self.zone} with value {self.value}."
|
|
569
586
|
}
|
|
570
587
|
) from None
|
|
571
588
|
|
|
589
|
+
def handle_conflicting_address_records(self):
|
|
590
|
+
if self.ipam_ip_address is None or not self.is_active:
|
|
591
|
+
return
|
|
592
|
+
|
|
593
|
+
if not get_plugin_config("netbox_dns", "autodns_conflict_deactivate", False):
|
|
594
|
+
return
|
|
595
|
+
|
|
596
|
+
records = Record.objects.filter(
|
|
597
|
+
zone=self.zone,
|
|
598
|
+
name=self.name,
|
|
599
|
+
type=self.type,
|
|
600
|
+
value=self.value,
|
|
601
|
+
status__in=Record.ACTIVE_STATUS_LIST,
|
|
602
|
+
ipam_ip_address__isnull=True,
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
for record in records:
|
|
606
|
+
record.status = RecordStatusChoices.STATUS_INACTIVE
|
|
607
|
+
record.save(update_fields=["status"])
|
|
608
|
+
|
|
572
609
|
def check_unique_rrset_ttl(self):
|
|
573
610
|
if self.pk is not None:
|
|
574
611
|
return
|
|
@@ -587,8 +624,12 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
587
624
|
)
|
|
588
625
|
.exclude(ttl=self.ttl)
|
|
589
626
|
.exclude(type=RecordTypeChoices.PTR, managed=True)
|
|
627
|
+
.exclude(status=RecordStatusChoices.STATUS_INACTIVE)
|
|
590
628
|
)
|
|
591
629
|
|
|
630
|
+
if self.ipam_ip_address is not None:
|
|
631
|
+
records = records.exclude(ipam_ip_address__isnull=False)
|
|
632
|
+
|
|
592
633
|
if not records.exists():
|
|
593
634
|
return
|
|
594
635
|
|
|
@@ -621,6 +662,7 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
621
662
|
.exclude(pk=self.pk)
|
|
622
663
|
.exclude(ttl=ttl)
|
|
623
664
|
.exclude(type=RecordTypeChoices.PTR, managed=True)
|
|
665
|
+
.exclude(status=RecordStatusChoices.STATUS_INACTIVE)
|
|
624
666
|
)
|
|
625
667
|
|
|
626
668
|
for record in records:
|
|
@@ -631,10 +673,10 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
631
673
|
self.type = self.type.upper()
|
|
632
674
|
super().clean_fields(*args, **kwargs)
|
|
633
675
|
|
|
634
|
-
def clean(self, *args, **kwargs):
|
|
635
|
-
self.validate_name()
|
|
676
|
+
def clean(self, *args, new_zone=None, **kwargs):
|
|
677
|
+
self.validate_name(new_zone=new_zone)
|
|
636
678
|
self.validate_value()
|
|
637
|
-
self.check_unique_record()
|
|
679
|
+
self.check_unique_record(new_zone=new_zone)
|
|
638
680
|
if self.pk is None:
|
|
639
681
|
self.check_unique_rrset_ttl()
|
|
640
682
|
|
|
@@ -740,6 +782,7 @@ class Record(ObjectModificationMixin, NetBoxModel):
|
|
|
740
782
|
self.ip_address = None
|
|
741
783
|
|
|
742
784
|
if self.is_address_record:
|
|
785
|
+
self.handle_conflicting_address_records()
|
|
743
786
|
self.update_ptr_record(
|
|
744
787
|
update_rfc2317_cname=update_rfc2317_cname,
|
|
745
788
|
save_zone_serial=save_zone_serial,
|
netbox_dns/models/zone.py
CHANGED
|
@@ -28,6 +28,7 @@ from netbox_dns.choices import RecordClassChoices, RecordTypeChoices, ZoneStatus
|
|
|
28
28
|
from netbox_dns.fields import NetworkField, RFC2317NetworkField
|
|
29
29
|
from netbox_dns.utilities import (
|
|
30
30
|
update_dns_records,
|
|
31
|
+
check_dns_records,
|
|
31
32
|
get_ip_addresses_by_zone,
|
|
32
33
|
arpa_to_prefix,
|
|
33
34
|
name_to_unicode,
|
|
@@ -652,6 +653,24 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
652
653
|
}
|
|
653
654
|
)
|
|
654
655
|
|
|
656
|
+
if old_zone.name != self.name or old_zone.view != self.view:
|
|
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(
|
|
665
|
+
ipam_ip_address__isnull=False
|
|
666
|
+
)
|
|
667
|
+
)
|
|
668
|
+
for ip_address in ip_addresses:
|
|
669
|
+
try:
|
|
670
|
+
check_dns_records(ip_address, zone=self)
|
|
671
|
+
except ValidationError as exc:
|
|
672
|
+
raise ValidationError(exc.messages)
|
|
673
|
+
|
|
655
674
|
if self.is_reverse_zone:
|
|
656
675
|
self.arpa_network = self.network_from_name
|
|
657
676
|
|
|
@@ -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
|
|
@@ -11,6 +12,8 @@ from utilities.exceptions import AbortRequest
|
|
|
11
12
|
|
|
12
13
|
from netbox_dns.models import view as _view
|
|
13
14
|
from netbox_dns.utilities import (
|
|
15
|
+
check_dns_records,
|
|
16
|
+
check_record_permission,
|
|
14
17
|
update_dns_records,
|
|
15
18
|
delete_dns_records,
|
|
16
19
|
get_views_by_prefix,
|
|
@@ -18,22 +21,89 @@ from netbox_dns.utilities import (
|
|
|
18
21
|
get_ip_addresses_by_view,
|
|
19
22
|
)
|
|
20
23
|
|
|
24
|
+
AUTODNS_CUSTOM_FIELDS = {
|
|
25
|
+
"ipaddress_dns_disabled": False,
|
|
26
|
+
"ipaddress_dns_record_ttl": None,
|
|
27
|
+
"ipaddress_dns_record_disable_ptr": False,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
IPADDRESS_ACTIVE_STATUS = settings.PLUGINS_CONFIG["netbox_dns"][
|
|
31
|
+
"autodns_ipaddress_active_status"
|
|
32
|
+
]
|
|
33
|
+
ENFORCE_UNIQUE_RECORDS = settings.PLUGINS_CONFIG["netbox_dns"]["enforce_unique_records"]
|
|
34
|
+
|
|
21
35
|
|
|
22
36
|
@receiver(post_clean, sender=IPAddress)
|
|
23
37
|
def ipam_autodns_ipaddress_post_clean(instance, **kwargs):
|
|
24
38
|
if not isinstance(instance.address, IPNetwork):
|
|
25
39
|
return
|
|
26
40
|
|
|
41
|
+
if instance.custom_field_data.get("ipaddress_dns_disabled"):
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# +
|
|
45
|
+
# Check for uniqueness of IP address and dns_name. If unique records are
|
|
46
|
+
# enforced, report an error when trying to create the same IP address with
|
|
47
|
+
# the same dns_name. Ignore existing IP addresses that have their CF
|
|
48
|
+
# "ipaddress_dns_disabled" set to "True".
|
|
49
|
+
# -
|
|
50
|
+
duplicate_addresses = IPAddress.objects.filter(
|
|
51
|
+
address=instance.address,
|
|
52
|
+
vrf=instance.vrf,
|
|
53
|
+
dns_name=instance.dns_name,
|
|
54
|
+
status__in=IPADDRESS_ACTIVE_STATUS,
|
|
55
|
+
)
|
|
56
|
+
if instance.pk is not None:
|
|
57
|
+
duplicate_addresses = duplicate_addresses.exclude(pk=instance.pk)
|
|
58
|
+
|
|
59
|
+
if ENFORCE_UNIQUE_RECORDS and instance.status in IPADDRESS_ACTIVE_STATUS:
|
|
60
|
+
for ip_address in duplicate_addresses.only("custom_field_data"):
|
|
61
|
+
if not ip_address.custom_field_data.get("ipaddress_dns_disabled"):
|
|
62
|
+
raise ValidationError(
|
|
63
|
+
{
|
|
64
|
+
"dns_name": "Unique DNS records are enforced and there is already "
|
|
65
|
+
f"an active IP address {instance.address} with DNS name {instance.dns_name}. "
|
|
66
|
+
"Plesase choose a different name or disable record creation for this "
|
|
67
|
+
"IP address."
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
# +
|
|
72
|
+
# Check NetBox DNS record permission for changes to IPAddress custom fields
|
|
73
|
+
#
|
|
74
|
+
# Normally, as the modfication of DNS fields
|
|
75
|
+
if (request := current_request.get()) is not None:
|
|
76
|
+
cf_data = instance.custom_field_data
|
|
77
|
+
if (
|
|
78
|
+
instance.pk is not None
|
|
79
|
+
and any(
|
|
80
|
+
(
|
|
81
|
+
cf_data.get(cf, cf_default)
|
|
82
|
+
!= IPAddress.objects.get(pk=instance.pk).custom_field_data.get(
|
|
83
|
+
cf, cf_default
|
|
84
|
+
)
|
|
85
|
+
for cf, cf_default in AUTODNS_CUSTOM_FIELDS.items()
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
and not check_record_permission()
|
|
89
|
+
) or (
|
|
90
|
+
instance.pk is None
|
|
91
|
+
and any(
|
|
92
|
+
(
|
|
93
|
+
cf_data.get(cf, cf_default) != cf_default
|
|
94
|
+
for cf, cf_default in AUTODNS_CUSTOM_FIELDS.items()
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
and not check_record_permission(change=False, delete=False)
|
|
98
|
+
):
|
|
99
|
+
raise ValidationError(
|
|
100
|
+
f"User '{request.user}' is not allowed to alter AutoDNS custom fields"
|
|
101
|
+
)
|
|
102
|
+
|
|
27
103
|
try:
|
|
28
|
-
|
|
104
|
+
check_dns_records(instance)
|
|
29
105
|
except ValidationError as exc:
|
|
30
|
-
|
|
31
|
-
for field in ("name", "ttl", "value", "type"):
|
|
32
|
-
value = exc.error_dict.pop(field, None)
|
|
33
|
-
if value is not None:
|
|
34
|
-
raise ValidationError({"dns_name": value})
|
|
35
|
-
|
|
36
|
-
raise exc
|
|
106
|
+
raise ValidationError({"dns_name": exc.messages})
|
|
37
107
|
|
|
38
108
|
|
|
39
109
|
@receiver(pre_delete, sender=IPAddress)
|
|
@@ -42,8 +112,8 @@ def ipam_autodns_ipaddress_pre_delete(instance, **kwargs):
|
|
|
42
112
|
|
|
43
113
|
|
|
44
114
|
@receiver(pre_save, sender=IPAddress)
|
|
45
|
-
def
|
|
46
|
-
|
|
115
|
+
def ipam_autodns_ipaddress_pre_save(instance, **kwargs):
|
|
116
|
+
check_dns_records(instance)
|
|
47
117
|
|
|
48
118
|
|
|
49
119
|
@receiver(post_save, sender=IPAddress)
|
|
@@ -55,16 +125,29 @@ def ipam_autodns_ipaddress_post_save(instance, **kwargs):
|
|
|
55
125
|
def ipam_autodns_prefix_pre_save(instance, **kwargs):
|
|
56
126
|
"""
|
|
57
127
|
Changes that modify the prefix hierarchy cannot be validated properly before
|
|
58
|
-
commiting them. So the solution in this case is to
|
|
59
|
-
|
|
128
|
+
commiting them. So the solution in this case is to ask the user to deassign
|
|
129
|
+
the prefix from any views it is assigned to and retry.
|
|
60
130
|
"""
|
|
131
|
+
request = current_request.get()
|
|
132
|
+
|
|
61
133
|
if instance.pk is None or not instance.netbox_dns_views.exists():
|
|
62
134
|
return
|
|
63
135
|
|
|
64
|
-
saved_prefix = Prefix.objects.get(
|
|
136
|
+
saved_prefix = Prefix.objects.prefetch_related("netbox_dns_views").get(
|
|
137
|
+
pk=instance.pk
|
|
138
|
+
)
|
|
65
139
|
if saved_prefix.prefix != instance.prefix or saved_prefix.vrf != instance.vrf:
|
|
66
|
-
for view in
|
|
67
|
-
|
|
140
|
+
dns_views = ", ".join([view.name for view in instance.netbox_dns_views.all()])
|
|
141
|
+
if request is not None:
|
|
142
|
+
raise AbortRequest(
|
|
143
|
+
f"This prefix is currently assigned to the following DNS views: {dns_views}"
|
|
144
|
+
f"Please deassign it from these views before making changes to the prefix "
|
|
145
|
+
f"or VRF."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
raise ValidationError(
|
|
149
|
+
f"Prefix is assigned to DNS views {dns_views}. Prefix and VRF must not be changed"
|
|
150
|
+
)
|
|
68
151
|
|
|
69
152
|
|
|
70
153
|
@receiver(pre_delete, sender=Prefix)
|
|
@@ -80,7 +163,7 @@ def ipam_autodns_prefix_pre_delete(instance, **kwargs):
|
|
|
80
163
|
_depth=instance.depth + 1, netbox_dns_views__isnull=True
|
|
81
164
|
):
|
|
82
165
|
for ip_address in get_ip_addresses_by_prefix(prefix):
|
|
83
|
-
|
|
166
|
+
check_dns_records(ip_address)
|
|
84
167
|
except ValidationError as exc:
|
|
85
168
|
if request is not None:
|
|
86
169
|
raise AbortRequest(
|
netbox_dns/template_content.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
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
|
|
@@ -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"
|
|
@@ -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 %}
|
|
@@ -45,15 +45,11 @@
|
|
|
45
45
|
{% for prefix in object.prefixes.all %}
|
|
46
46
|
<tr>
|
|
47
47
|
<td>
|
|
48
|
-
|
|
49
|
-
{{ prefix }}
|
|
50
|
-
</a>
|
|
48
|
+
{{ prefix|linkify }}
|
|
51
49
|
</td>
|
|
52
50
|
{% if prefix.vrf %}
|
|
53
51
|
<td>
|
|
54
|
-
|
|
55
|
-
{{ prefix.vrf }}
|
|
56
|
-
</a>
|
|
52
|
+
{{ prefix.vrf|linkify }}
|
|
57
53
|
</td>
|
|
58
54
|
{% else %}
|
|
59
55
|
<td>Global</td>
|
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
|
]
|
|
@@ -7,6 +7,7 @@ from dns import name as dns_name
|
|
|
7
7
|
from django.conf import settings
|
|
8
8
|
from django.db.models import Q
|
|
9
9
|
|
|
10
|
+
from netbox.context import current_request
|
|
10
11
|
from ipam.models import IPAddress, Prefix
|
|
11
12
|
|
|
12
13
|
from netbox_dns.models import zone as _zone
|
|
@@ -16,12 +17,14 @@ from netbox_dns.models import view as _view
|
|
|
16
17
|
|
|
17
18
|
__all__ = (
|
|
18
19
|
"get_zones",
|
|
20
|
+
"check_dns_records",
|
|
19
21
|
"update_dns_records",
|
|
20
22
|
"delete_dns_records",
|
|
21
23
|
"get_views_by_prefix",
|
|
22
24
|
"get_ip_addresses_by_prefix",
|
|
23
25
|
"get_ip_addresses_by_view",
|
|
24
26
|
"get_ip_addresses_by_zone",
|
|
27
|
+
"check_record_permission",
|
|
25
28
|
)
|
|
26
29
|
|
|
27
30
|
|
|
@@ -49,7 +52,13 @@ def _get_record_status(ip_address):
|
|
|
49
52
|
)
|
|
50
53
|
|
|
51
54
|
|
|
52
|
-
def
|
|
55
|
+
def _valid_entry(ip_address, zone):
|
|
56
|
+
return zone.view in _get_assigned_views(ip_address) and dns_name.from_text(
|
|
57
|
+
ip_address.dns_name
|
|
58
|
+
).is_subdomain(dns_name.from_text(zone.name))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_zones(ip_address, view=None, old_zone=None):
|
|
53
62
|
if view is None:
|
|
54
63
|
views = _get_assigned_views(ip_address)
|
|
55
64
|
if not views:
|
|
@@ -69,10 +78,13 @@ def get_zones(ip_address, view=None):
|
|
|
69
78
|
active=True,
|
|
70
79
|
)
|
|
71
80
|
|
|
72
|
-
if not zones:
|
|
73
|
-
return []
|
|
74
|
-
|
|
75
81
|
zone_map = defaultdict(list)
|
|
82
|
+
|
|
83
|
+
if old_zone is not None:
|
|
84
|
+
zones = zones.exclude(pk=old_zone.pk)
|
|
85
|
+
if _valid_entry(ip_address, old_zone):
|
|
86
|
+
zone_map[old_zone.view].append(old_zone)
|
|
87
|
+
|
|
76
88
|
for zone in zones:
|
|
77
89
|
zone_map[zone.view].append(zone)
|
|
78
90
|
|
|
@@ -82,19 +94,68 @@ def get_zones(ip_address, view=None):
|
|
|
82
94
|
]
|
|
83
95
|
|
|
84
96
|
|
|
85
|
-
def
|
|
97
|
+
def check_dns_records(ip_address, zone=None, view=None):
|
|
86
98
|
if ip_address.dns_name == "":
|
|
87
|
-
if commit:
|
|
88
|
-
delete_dns_records(ip_address)
|
|
89
99
|
return
|
|
90
100
|
|
|
91
|
-
|
|
101
|
+
if zone is None:
|
|
102
|
+
zones = get_zones(ip_address, view=view)
|
|
103
|
+
|
|
104
|
+
if ip_address.pk is not None:
|
|
105
|
+
for record in ip_address.netbox_dns_records.filter(zone__in=zones):
|
|
106
|
+
if (
|
|
107
|
+
record.fqdn != ip_address.dns_name
|
|
108
|
+
or record.value != ip_address.address.ip
|
|
109
|
+
or record.status != _get_record_status(ip_address)
|
|
110
|
+
):
|
|
111
|
+
record.update_from_ip_address(ip_address)
|
|
112
|
+
|
|
113
|
+
if record is not None:
|
|
114
|
+
record.clean()
|
|
115
|
+
|
|
116
|
+
zones = _zone.Zone.objects.filter(
|
|
117
|
+
pk__in=[zone.pk for zone in zones]
|
|
118
|
+
).exclude(
|
|
119
|
+
pk__in=set(
|
|
120
|
+
ip_address.netbox_dns_records.all().values_list("zone", flat=True)
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
for zone in zones:
|
|
125
|
+
record = _record.Record.create_from_ip_address(
|
|
126
|
+
ip_address,
|
|
127
|
+
zone,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
if record is not None:
|
|
131
|
+
record.clean()
|
|
132
|
+
|
|
133
|
+
if ip_address.pk is None:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
new_zone = get_zones(ip_address, old_zone=zone)[0]
|
|
138
|
+
except IndexError:
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
for record in ip_address.netbox_dns_records.filter(zone=zone):
|
|
142
|
+
record.update_from_ip_address(ip_address, new_zone)
|
|
143
|
+
|
|
144
|
+
if record is not None:
|
|
145
|
+
record.clean(new_zone=new_zone)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def update_dns_records(ip_address):
|
|
149
|
+
if ip_address.dns_name == "":
|
|
150
|
+
delete_dns_records(ip_address)
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
zones = get_zones(ip_address)
|
|
92
154
|
|
|
93
155
|
if ip_address.pk is not None:
|
|
94
156
|
for record in ip_address.netbox_dns_records.all():
|
|
95
157
|
if record.zone not in zones:
|
|
96
|
-
|
|
97
|
-
record.delete()
|
|
158
|
+
record.delete()
|
|
98
159
|
continue
|
|
99
160
|
|
|
100
161
|
if (
|
|
@@ -105,13 +166,12 @@ def update_dns_records(ip_address, commit=True, view=None):
|
|
|
105
166
|
record.update_from_ip_address(ip_address)
|
|
106
167
|
|
|
107
168
|
if record is not None:
|
|
108
|
-
|
|
109
|
-
record.save()
|
|
110
|
-
else:
|
|
111
|
-
record.clean()
|
|
169
|
+
record.save()
|
|
112
170
|
|
|
113
|
-
zones =
|
|
114
|
-
|
|
171
|
+
zones = _zone.Zone.objects.filter(pk__in=[zone.pk for zone in zones]).exclude(
|
|
172
|
+
pk__in=set(
|
|
173
|
+
ip_address.netbox_dns_records.all().values_list("zone", flat=True)
|
|
174
|
+
)
|
|
115
175
|
)
|
|
116
176
|
|
|
117
177
|
for zone in zones:
|
|
@@ -121,10 +181,7 @@ def update_dns_records(ip_address, commit=True, view=None):
|
|
|
121
181
|
)
|
|
122
182
|
|
|
123
183
|
if record is not None:
|
|
124
|
-
|
|
125
|
-
record.save()
|
|
126
|
-
else:
|
|
127
|
-
record.clean()
|
|
184
|
+
record.save()
|
|
128
185
|
|
|
129
186
|
|
|
130
187
|
def delete_dns_records(ip_address):
|
|
@@ -203,3 +260,20 @@ def get_ip_addresses_by_zone(zone):
|
|
|
203
260
|
queryset = get_ip_addresses_by_view(zone.view)
|
|
204
261
|
|
|
205
262
|
return queryset.filter(dns_name__regex=rf"\.{re.escape(zone.name)}\.?$")
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def check_record_permission(add=True, change=True, delete=True):
|
|
266
|
+
checks = locals().copy()
|
|
267
|
+
|
|
268
|
+
request = current_request.get()
|
|
269
|
+
|
|
270
|
+
if request is None:
|
|
271
|
+
return True
|
|
272
|
+
|
|
273
|
+
return all(
|
|
274
|
+
(
|
|
275
|
+
request.user.has_perm(f"netbox_dns.{perm}_record")
|
|
276
|
+
for perm, check in checks.items()
|
|
277
|
+
if check
|
|
278
|
+
)
|
|
279
|
+
)
|
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,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: netbox-plugin-dns
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.1b3
|
|
4
4
|
Summary: NetBox DNS is a NetBox plugin for managing DNS data.
|
|
5
5
|
Home-page: https://github.com/peteeckel/netbox-plugin-dns
|
|
6
6
|
License: MIT
|
|
@@ -8,7 +8,7 @@ Keywords: netbox,netbox-plugin,dns
|
|
|
8
8
|
Author: Peter Eckel
|
|
9
9
|
Author-email: pete@netbox-dns.org
|
|
10
10
|
Requires-Python: >=3.10,<4.0
|
|
11
|
-
Classifier: Development Status ::
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.10
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
netbox_dns/__init__.py,sha256=
|
|
1
|
+
netbox_dns/__init__.py,sha256=Ck45UmyPbGnlkricEwMjOpBDT-BBjVBZfmKi3AbwsxM,1833
|
|
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
|
|
@@ -37,8 +37,8 @@ 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=AMTBIw8uNTjDdq8QZTxyw-e8KfQ43SVkO2tc3og4vt8,9069
|
|
41
|
+
netbox_dns/forms/zone.py,sha256=17Ii2csnYquuz7HGgaK36ZgOzWnFXFeh1IQYvWnBKC0,23537
|
|
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
|
|
@@ -46,7 +46,7 @@ netbox_dns/graphql/schema.py,sha256=P-oQ8ei3sC6XLhgCa_riRbRTrMkPCVTJXkGv0U2rPYw,
|
|
|
46
46
|
netbox_dns/graphql/types.py,sha256=4ewWOqEbWtCBiU9bdIm_6CIm6MKAM6szCAXSvokpqWg,6108
|
|
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/setup_autodns.py,sha256=
|
|
49
|
+
netbox_dns/management/commands/setup_autodns.py,sha256=8ipEDyvZ0MIzkcj9gASModRbPHKyKCnyfke2SYOl61g,5733
|
|
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,15 +72,15 @@ 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=ArQp7gB94FZH9MeihfIbx9pN3Y90gSj4VsoSF6y1348,25966
|
|
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/ipam_autodns.py,sha256=
|
|
83
|
+
netbox_dns/signals/ipam_autodns.py,sha256=XkngO_hbfi_kzb7pnplQbOYp8TcUvpP1j3R49rWy7Go,7908
|
|
84
84
|
netbox_dns/tables/__init__.py,sha256=s41w4o77tIwmhnLjsOsg08R9m3wrlomkkfCLTVQuPzc,196
|
|
85
85
|
netbox_dns/tables/contact.py,sha256=sPs7d1ZhVC5dOS37dPYFqebNd7WGvsV_eYzX_TMcbzY,804
|
|
86
86
|
netbox_dns/tables/nameserver.py,sha256=fFiE-yH-_GyRDaV4SVw094r6uH58Kx56NSWDGaMR58g,764
|
|
@@ -90,7 +90,7 @@ netbox_dns/tables/registrar.py,sha256=M-ckyQUs6dqjTCPf7bAr6UuLEA-q9f9CxKW7yp3rGo
|
|
|
90
90
|
netbox_dns/tables/view.py,sha256=jf2S4TiOdMq6-wWk0ndR1uBJpkOx_f3pqAuM1nSXTBo,1178
|
|
91
91
|
netbox_dns/tables/zone.py,sha256=IeCiflrQBn1INV_PxoTySWQrDalykY4mDSG76VXC5WM,1877
|
|
92
92
|
netbox_dns/tables/zone_template.py,sha256=70hvS-xpeaLkcM6y0R9xsUMQVKgTgZJaWWNd99BfmzI,1479
|
|
93
|
-
netbox_dns/template_content.py,sha256=
|
|
93
|
+
netbox_dns/template_content.py,sha256=a2TAwrw0TeDICv8vSiAuzeeuBEqPbahm_OfFkYGrG-s,3742
|
|
94
94
|
netbox_dns/templates/netbox_dns/contact.html,sha256=fMHAQyLXIxohKoCTxFEnKetl9UVXeQgjasfpv_JONaw,2855
|
|
95
95
|
netbox_dns/templates/netbox_dns/nameserver.html,sha256=DpTdetQVV_jKThDbi62LvbhiCay-1QxR-yiJEiPFm4w,1554
|
|
96
96
|
netbox_dns/templates/netbox_dns/record/managed.html,sha256=G6LPG1koUGuzUiwYdv1okdVa4sKaofiQegDBnsFL0kA,89
|
|
@@ -98,8 +98,10 @@ netbox_dns/templates/netbox_dns/record/related.html,sha256=Aqor8uGcuHQTHjlX-Xmni
|
|
|
98
98
|
netbox_dns/templates/netbox_dns/record.html,sha256=o3z_D6Fqqn7nx1IwPXKQ75ZaPhU6kae0WpaWa3UMcxQ,5211
|
|
99
99
|
netbox_dns/templates/netbox_dns/recordtemplate.html,sha256=9tkXtKqa5p3LdOU9REm99WSFwGJaH8OczpIqXZuXMcg,3099
|
|
100
100
|
netbox_dns/templates/netbox_dns/registrar.html,sha256=O5veGmW59Pf5yN25ihPLvRIkA2P7xmSGv0G3NrRG8vI,2152
|
|
101
|
+
netbox_dns/templates/netbox_dns/view/button.html,sha256=oXKNyPtY8XIu2sxtZWpFRXKXv862407ESyUQ4YsWCGE,292
|
|
102
|
+
netbox_dns/templates/netbox_dns/view/prefix.html,sha256=HD8f4mnbzFOXDj3Y_yq8yEeDpz_yFud8ZMpqbxzCEnA,1445
|
|
101
103
|
netbox_dns/templates/netbox_dns/view/related.html,sha256=W9Ie2aOsFkWyYtBnZn38seQDBmyJkV9dqFDG-Dq3yMk,736
|
|
102
|
-
netbox_dns/templates/netbox_dns/view.html,sha256=
|
|
104
|
+
netbox_dns/templates/netbox_dns/view.html,sha256=NSEfPSHPLw5yjUSat9N_KYKF5FezmTlCXqPC6FYiK9E,2479
|
|
103
105
|
netbox_dns/templates/netbox_dns/zone/base.html,sha256=n_E4aVYdGeZZl-ARE8sb4DgAAgPs92X1UEFepX3xIlM,495
|
|
104
106
|
netbox_dns/templates/netbox_dns/zone/child.html,sha256=kH56PJFBGCjiRdIh7zCtClnZdfOChqN_sYslsyoz5gU,2147
|
|
105
107
|
netbox_dns/templates/netbox_dns/zone/child_zone.html,sha256=b9CSGWEfWT7hLQ80gApMnu7mXM8w2LT-3UaOYe6HIRQ,510
|
|
@@ -115,12 +117,12 @@ netbox_dns/urls/nameserver.py,sha256=BBbY-wqPqCquvLLv1_JhqToj7oDHhPNGCWHt0IfjBNM
|
|
|
115
117
|
netbox_dns/urls/record.py,sha256=bDprohTso1N0GtPXH4X3TNHnkxopiOSQFXWItifEZ_k,1432
|
|
116
118
|
netbox_dns/urls/record_template.py,sha256=Z-7aA-rPIxRBCmXNUiQcHIgjYfai28Tf_sLtkl2ihDk,1827
|
|
117
119
|
netbox_dns/urls/registrar.py,sha256=u6B0zGGYNUJIKTo9uGiUeZLPD0QMGaQOAPShGEy4NaA,1728
|
|
118
|
-
netbox_dns/urls/view.py,sha256=
|
|
120
|
+
netbox_dns/urls/view.py,sha256=jz5ANOOLCMAcWermTZYGq9BvnP02jpKGL6hCm33C47Q,1478
|
|
119
121
|
netbox_dns/urls/zone.py,sha256=rmB1BkzmWNG06ILUf-39Aj6-SBFkwQouyixMQiamqPc,2005
|
|
120
122
|
netbox_dns/urls/zone_template.py,sha256=w3Gu8qfLCWyHofeLkGZd1HpYSlcslomVlBQJZyqh8kk,1690
|
|
121
123
|
netbox_dns/utilities/__init__.py,sha256=M9T8PUFlGddtENzEznHAPbEsz1VFrPcmbD-BGLCsvB4,55
|
|
122
124
|
netbox_dns/utilities/conversions.py,sha256=NS37SoMqXc13wNWRkKnLfyQbVi6QKD33fu5ovTKRo74,1979
|
|
123
|
-
netbox_dns/utilities/ipam_autodns.py,sha256=
|
|
125
|
+
netbox_dns/utilities/ipam_autodns.py,sha256=QMCMZkY8YxZL3VeH5xHCQWWd3Cji9Nx0GTUcsjtXsQY,8137
|
|
124
126
|
netbox_dns/validators/__init__.py,sha256=Mr8TvmcJTa8Pubj8TzbFBKfbHhEmGcr5JdQvczEJ39A,72
|
|
125
127
|
netbox_dns/validators/dns_name.py,sha256=B4A0BOW5pKDjjukvksriRtnLzkYTx_pFjh7eqKo6PBE,3069
|
|
126
128
|
netbox_dns/validators/dns_value.py,sha256=y2Zga4hmywqDrTBXcMC-sWaFbw4eoY8pySq7cWnMP8Y,2822
|
|
@@ -131,10 +133,10 @@ netbox_dns/views/nameserver.py,sha256=DFr0eybMshc1FW06g4cy9Nk4VRMxRqakI5KtHFiAVR
|
|
|
131
133
|
netbox_dns/views/record.py,sha256=fHMafCC14C7d6oXbXc2vN-T70OAOaTY77_m3Dct-oiQ,4590
|
|
132
134
|
netbox_dns/views/record_template.py,sha256=BkemTBEramLhYqB6HrA80sNgtduW1ZOJwbYs3i7srik,2510
|
|
133
135
|
netbox_dns/views/registrar.py,sha256=yRQgFm3vgBD21ZQex9asjs0QWegvSHlcyHXLnjvc5xs,2324
|
|
134
|
-
netbox_dns/views/view.py,sha256=
|
|
136
|
+
netbox_dns/views/view.py,sha256=iXBJTc3JD5cD5z0RTcHVTtYV-KNIJGneeoxymXChdUE,2759
|
|
135
137
|
netbox_dns/views/zone.py,sha256=SKhf_WHcFVpKqFTuUMf-Dmxu1AwFHBeo_DtD8UGFrJ8,5483
|
|
136
138
|
netbox_dns/views/zone_template.py,sha256=qvXl-bpc1fMc1WFngynj4-Q3-JJDgKdT-r54s4M1D0s,2118
|
|
137
|
-
netbox_plugin_dns-1.
|
|
138
|
-
netbox_plugin_dns-1.
|
|
139
|
-
netbox_plugin_dns-1.
|
|
140
|
-
netbox_plugin_dns-1.
|
|
139
|
+
netbox_plugin_dns-1.1b3.dist-info/LICENSE,sha256=I3tDu11bZfhFm3EkV4zOD5TmWgLjnUNLEFwrdjniZYs,1112
|
|
140
|
+
netbox_plugin_dns-1.1b3.dist-info/METADATA,sha256=TZ6wKgEaZXVAH0j4IZsRrlfmQZ_kQjhy72ATDEvQmag,6404
|
|
141
|
+
netbox_plugin_dns-1.1b3.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
142
|
+
netbox_plugin_dns-1.1b3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|