netbox-plugin-dns 0.22.4__py3-none-any.whl → 0.22.6__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 -39
- netbox_dns/api/serializers.py +1 -0
- netbox_dns/filters/registrar.py +1 -0
- netbox_dns/forms/registrar.py +11 -1
- netbox_dns/forms/zone.py +31 -21
- netbox_dns/management/commands/cleanup_rrset_ttl.py +62 -0
- netbox_dns/models/record.py +151 -25
- netbox_dns/models/zone.py +82 -18
- netbox_dns/templates/netbox_dns/record.html +6 -22
- netbox_dns/templates/netbox_dns/registrar.html +4 -0
- netbox_dns/templates/netbox_dns/zone.html +7 -0
- netbox_dns/views/record.py +76 -2
- {netbox_plugin_dns-0.22.4.dist-info → netbox_plugin_dns-0.22.6.dist-info}/METADATA +11 -11
- {netbox_plugin_dns-0.22.4.dist-info → netbox_plugin_dns-0.22.6.dist-info}/RECORD +16 -15
- {netbox_plugin_dns-0.22.4.dist-info → netbox_plugin_dns-0.22.6.dist-info}/LICENSE +0 -0
- {netbox_plugin_dns-0.22.4.dist-info → netbox_plugin_dns-0.22.6.dist-info}/WHEEL +0 -0
netbox_dns/__init__.py
CHANGED
|
@@ -10,7 +10,7 @@ except ImportError:
|
|
|
10
10
|
# NetBox 3.5.8
|
|
11
11
|
from extras.plugins.utils import get_plugin_config
|
|
12
12
|
|
|
13
|
-
__version__ = "0.22.
|
|
13
|
+
__version__ = "0.22.6"
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class DNSConfig(PluginConfig):
|
|
@@ -38,47 +38,11 @@ class DNSConfig(PluginConfig):
|
|
|
38
38
|
],
|
|
39
39
|
"tolerate_non_rfc1035_types": [],
|
|
40
40
|
"enable_root_zones": False,
|
|
41
|
-
"enforce_unique_records":
|
|
41
|
+
"enforce_unique_records": True,
|
|
42
|
+
"enforce_unique_rrset_ttl": True,
|
|
42
43
|
}
|
|
43
44
|
base_url = "netbox-dns"
|
|
44
45
|
|
|
45
|
-
def ready(self):
|
|
46
|
-
#
|
|
47
|
-
# Check if required custom fields exist for IPAM coupling
|
|
48
|
-
#
|
|
49
|
-
if get_plugin_config("netbox_dns", "feature_ipam_coupling"):
|
|
50
|
-
from extras.models import CustomField
|
|
51
|
-
from ipam.models import IPAddress
|
|
52
|
-
from django.contrib.contenttypes.models import ContentType
|
|
53
|
-
|
|
54
|
-
try:
|
|
55
|
-
objtype = ContentType.objects.get_for_model(IPAddress)
|
|
56
|
-
required_cf = (
|
|
57
|
-
"ipaddress_dns_record_name",
|
|
58
|
-
"ipaddress_dns_record_ttl",
|
|
59
|
-
"ipaddress_dns_record_disable_ptr",
|
|
60
|
-
"ipaddress_dns_zone_id",
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
if CustomField.objects.filter(
|
|
64
|
-
name__in=required_cf, content_types=objtype
|
|
65
|
-
).count() < len(required_cf):
|
|
66
|
-
print(
|
|
67
|
-
"WARNING: 'feature_ipam_coupling' is enabled, but the required"
|
|
68
|
-
" custom fields for IPAM DNS coupling are missing. Please run"
|
|
69
|
-
" the Django management command 'setup_coupling' to create the"
|
|
70
|
-
" missing custom fields.",
|
|
71
|
-
file=sys.stderr,
|
|
72
|
-
)
|
|
73
|
-
except OperationalError as exc:
|
|
74
|
-
print(
|
|
75
|
-
"WARNING: Unable to connect to PostgreSQL, cannot check custom fields"
|
|
76
|
-
" for feature_ipam_coupling",
|
|
77
|
-
file=sys.stderr,
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
super().ready()
|
|
81
|
-
|
|
82
46
|
|
|
83
47
|
#
|
|
84
48
|
# Initialize plugin config
|
netbox_dns/api/serializers.py
CHANGED
netbox_dns/filters/registrar.py
CHANGED
netbox_dns/forms/registrar.py
CHANGED
|
@@ -17,6 +17,7 @@ class RegistrarForm(NetBoxModelForm):
|
|
|
17
17
|
fields = (
|
|
18
18
|
"name",
|
|
19
19
|
"iana_id",
|
|
20
|
+
"address",
|
|
20
21
|
"referral_url",
|
|
21
22
|
"whois_server",
|
|
22
23
|
"abuse_email",
|
|
@@ -29,12 +30,18 @@ class RegistrarFilterForm(NetBoxModelFilterSetForm):
|
|
|
29
30
|
model = Registrar
|
|
30
31
|
fieldsets = (
|
|
31
32
|
(None, ("q", "name", "iana_id", "tags")),
|
|
32
|
-
(
|
|
33
|
+
(
|
|
34
|
+
"Contact",
|
|
35
|
+
("address", "referral_url", "whois_server", "abuse_email", "abuse_phone"),
|
|
36
|
+
),
|
|
33
37
|
)
|
|
34
38
|
|
|
35
39
|
name = forms.CharField(
|
|
36
40
|
required=False,
|
|
37
41
|
)
|
|
42
|
+
address = forms.CharField(
|
|
43
|
+
required=False,
|
|
44
|
+
)
|
|
38
45
|
iana_id = forms.IntegerField(
|
|
39
46
|
required=False,
|
|
40
47
|
label="IANA ID",
|
|
@@ -64,6 +71,7 @@ class RegistrarImportForm(NetBoxModelImportForm):
|
|
|
64
71
|
fields = (
|
|
65
72
|
"name",
|
|
66
73
|
"iana_id",
|
|
74
|
+
"address",
|
|
67
75
|
"referral_url",
|
|
68
76
|
"whois_server",
|
|
69
77
|
"abuse_email",
|
|
@@ -96,6 +104,7 @@ class RegistrarBulkEditForm(NetBoxModelBulkEditForm):
|
|
|
96
104
|
(
|
|
97
105
|
None,
|
|
98
106
|
(
|
|
107
|
+
"address",
|
|
99
108
|
"referral_url",
|
|
100
109
|
"whois_server",
|
|
101
110
|
"abuse_email",
|
|
@@ -105,6 +114,7 @@ class RegistrarBulkEditForm(NetBoxModelBulkEditForm):
|
|
|
105
114
|
)
|
|
106
115
|
|
|
107
116
|
nullable_fields = (
|
|
117
|
+
"address",
|
|
108
118
|
"referral_url",
|
|
109
119
|
"whois_server",
|
|
110
120
|
"abuse_email",
|
netbox_dns/forms/zone.py
CHANGED
|
@@ -315,7 +315,7 @@ class ZoneImportForm(NetBoxModelImportForm):
|
|
|
315
315
|
required=False,
|
|
316
316
|
help_text="Mailbox of the zone's administrator",
|
|
317
317
|
)
|
|
318
|
-
soa_serial_auto = forms.
|
|
318
|
+
soa_serial_auto = forms.NullBooleanField(
|
|
319
319
|
required=False,
|
|
320
320
|
help_text="Generate the SOA serial",
|
|
321
321
|
)
|
|
@@ -443,26 +443,6 @@ class ZoneImportForm(NetBoxModelImportForm):
|
|
|
443
443
|
def clean_soa_rname(self):
|
|
444
444
|
return self._clean_field_with_defaults("soa_rname")
|
|
445
445
|
|
|
446
|
-
def clean_soa_serial_auto(self):
|
|
447
|
-
try:
|
|
448
|
-
return self._clean_field_with_defaults("soa_serial_auto")
|
|
449
|
-
except ValidationError:
|
|
450
|
-
if self.cleaned_data["soa_serial"] or self._get_default_value("soa_serial"):
|
|
451
|
-
return None
|
|
452
|
-
|
|
453
|
-
raise
|
|
454
|
-
|
|
455
|
-
def clean_soa_serial(self):
|
|
456
|
-
try:
|
|
457
|
-
return self._clean_field_with_defaults("soa_serial")
|
|
458
|
-
except ValidationError:
|
|
459
|
-
if self.cleaned_data["soa_serial_auto"] or self._get_default_value(
|
|
460
|
-
"soa_serial_auto"
|
|
461
|
-
):
|
|
462
|
-
return None
|
|
463
|
-
|
|
464
|
-
raise
|
|
465
|
-
|
|
466
446
|
def clean_soa_refresh(self):
|
|
467
447
|
return self._clean_field_with_defaults("soa_refresh")
|
|
468
448
|
|
|
@@ -475,6 +455,36 @@ class ZoneImportForm(NetBoxModelImportForm):
|
|
|
475
455
|
def clean_soa_minimum(self):
|
|
476
456
|
return self._clean_field_with_defaults("soa_minimum")
|
|
477
457
|
|
|
458
|
+
def clean(self, *args, **kwargs):
|
|
459
|
+
super().clean(*args, **kwargs)
|
|
460
|
+
|
|
461
|
+
soa_serial_auto = self.cleaned_data.get("soa_serial_auto")
|
|
462
|
+
soa_serial = self.cleaned_data.get("soa_serial")
|
|
463
|
+
|
|
464
|
+
if soa_serial is None:
|
|
465
|
+
soa_serial = self._get_default_value("soa_serial")
|
|
466
|
+
|
|
467
|
+
if soa_serial_auto is None:
|
|
468
|
+
if self._get_default_value("soa_serial_auto") is not None:
|
|
469
|
+
soa_serial_auto = self._get_default_value("soa_serial_auto")
|
|
470
|
+
|
|
471
|
+
elif soa_serial is not None:
|
|
472
|
+
soa_serial_auto = False
|
|
473
|
+
|
|
474
|
+
else:
|
|
475
|
+
raise ValidationError(
|
|
476
|
+
"SOA Serial Auto not set and no default value and SOA Serial available"
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
if "soa_serial_auto" in self.cleaned_data:
|
|
480
|
+
self.cleaned_data["soa_serial_auto"] = soa_serial_auto
|
|
481
|
+
|
|
482
|
+
if "soa_serial" in self.cleaned_data:
|
|
483
|
+
if soa_serial_auto:
|
|
484
|
+
self.cleaned_data["soa_serial"] = None
|
|
485
|
+
else:
|
|
486
|
+
self.cleaned_data["soa_serial"] = soa_serial
|
|
487
|
+
|
|
478
488
|
class Meta:
|
|
479
489
|
model = Zone
|
|
480
490
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from django.core.management.base import BaseCommand
|
|
2
|
+
from django.db.models import Max, Min
|
|
3
|
+
|
|
4
|
+
from netbox_dns.models import (
|
|
5
|
+
Record,
|
|
6
|
+
RecordTypeChoices,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Command(BaseCommand):
|
|
11
|
+
help = "Clean up the TTLs for RRSets"
|
|
12
|
+
|
|
13
|
+
def add_arguments(self, parser):
|
|
14
|
+
min_max = parser.add_mutually_exclusive_group()
|
|
15
|
+
min_max.add_argument(
|
|
16
|
+
"--min",
|
|
17
|
+
action="store_true",
|
|
18
|
+
help="Use the minimum TTL of an RRSet for all Records",
|
|
19
|
+
)
|
|
20
|
+
min_max.add_argument(
|
|
21
|
+
"--max",
|
|
22
|
+
action="store_true",
|
|
23
|
+
help="Use the maximum TTL of an RRSet for all Records",
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
def handle(self, *model_names, **options):
|
|
27
|
+
self.cleanup_rrset_ttl(**options)
|
|
28
|
+
|
|
29
|
+
self.stdout.write("RRSet cleanup completed.")
|
|
30
|
+
|
|
31
|
+
def cleanup_rrset_ttl(self, **options):
|
|
32
|
+
verbose = options.get("verbosity") > 1
|
|
33
|
+
|
|
34
|
+
ttl_records = (
|
|
35
|
+
Record.objects.filter(ttl__isnull=False)
|
|
36
|
+
.exclude(type=RecordTypeChoices.SOA)
|
|
37
|
+
.exclude(type=RecordTypeChoices.PTR, maanged=True)
|
|
38
|
+
)
|
|
39
|
+
for record in ttl_records:
|
|
40
|
+
records = Record.objects.filter(
|
|
41
|
+
name=record.name,
|
|
42
|
+
zone=record.zone,
|
|
43
|
+
type=record.type,
|
|
44
|
+
).exclude(type=RecordTypeChoices.PTR, maanged=True)
|
|
45
|
+
|
|
46
|
+
if records.count() == 1:
|
|
47
|
+
if options.get("verbosity") > 2:
|
|
48
|
+
self.stdout.write(f"Ignoring single record {record.id} ({record})")
|
|
49
|
+
continue
|
|
50
|
+
|
|
51
|
+
if options.get("max"):
|
|
52
|
+
ttl = records.aggregate(Max("ttl")).get("ttl__max")
|
|
53
|
+
else:
|
|
54
|
+
ttl = records.aggregate(Min("ttl")).get("ttl__min")
|
|
55
|
+
|
|
56
|
+
for record in records.exclude(ttl=ttl):
|
|
57
|
+
if options.get("verbosity") > 1:
|
|
58
|
+
self.stdout.write(
|
|
59
|
+
f"Updating TTL for record {record.id} ({record}) to {ttl}"
|
|
60
|
+
)
|
|
61
|
+
record.ttl = ttl
|
|
62
|
+
record.save(update_fields=["ttl"], update_rrset_ttl=False)
|
netbox_dns/models/record.py
CHANGED
|
@@ -6,7 +6,7 @@ from dns import name as dns_name
|
|
|
6
6
|
|
|
7
7
|
from django.core.exceptions import ValidationError
|
|
8
8
|
from django.db import transaction, models
|
|
9
|
-
from django.db.models import Q, ExpressionWrapper, BooleanField
|
|
9
|
+
from django.db.models import Q, ExpressionWrapper, BooleanField, Min
|
|
10
10
|
from django.db.models.functions import Length
|
|
11
11
|
from django.urls import reverse
|
|
12
12
|
|
|
@@ -38,6 +38,10 @@ from netbox_dns.validators import (
|
|
|
38
38
|
import netbox_dns.models.zone as zone
|
|
39
39
|
|
|
40
40
|
|
|
41
|
+
def min_ttl(*ttl_list):
|
|
42
|
+
return min((ttl for ttl in ttl_list if ttl is not None), default=None)
|
|
43
|
+
|
|
44
|
+
|
|
41
45
|
class RecordManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|
42
46
|
"""Special Manager for records providing the activity status annotation"""
|
|
43
47
|
|
|
@@ -207,7 +211,8 @@ class Record(NetBoxModel):
|
|
|
207
211
|
try:
|
|
208
212
|
name = (
|
|
209
213
|
dns_name.from_text(
|
|
210
|
-
|
|
214
|
+
str(self.name),
|
|
215
|
+
origin=dns_name.from_text(self.zone.name, origin=None),
|
|
211
216
|
)
|
|
212
217
|
.relativize(dns_name.root)
|
|
213
218
|
.to_unicode()
|
|
@@ -236,6 +241,16 @@ class Record(NetBoxModel):
|
|
|
236
241
|
|
|
237
242
|
return name.to_text()
|
|
238
243
|
|
|
244
|
+
@property
|
|
245
|
+
def value_fqdn(self):
|
|
246
|
+
if self.type != RecordTypeChoices.CNAME:
|
|
247
|
+
return None
|
|
248
|
+
|
|
249
|
+
zone = dns_name.from_text(self.zone.name)
|
|
250
|
+
value_fqdn = dns_name.from_text(self.value, origin=zone)
|
|
251
|
+
|
|
252
|
+
return value_fqdn.to_text()
|
|
253
|
+
|
|
239
254
|
@property
|
|
240
255
|
def address_from_name(self):
|
|
241
256
|
prefix = arpa_to_prefix(self.fqdn)
|
|
@@ -309,7 +324,7 @@ class Record(NetBoxModel):
|
|
|
309
324
|
|
|
310
325
|
return ptr_zone
|
|
311
326
|
|
|
312
|
-
def update_ptr_record(self, update_rfc2317_cname=True):
|
|
327
|
+
def update_ptr_record(self, update_rfc2317_cname=True, save_zone_serial=True):
|
|
313
328
|
ptr_zone = self.ptr_zone
|
|
314
329
|
|
|
315
330
|
if (
|
|
@@ -339,14 +354,16 @@ class Record(NetBoxModel):
|
|
|
339
354
|
not ptr_record.zone.is_rfc2317_zone
|
|
340
355
|
and ptr_record.rfc2317_cname_record is not None
|
|
341
356
|
):
|
|
342
|
-
ptr_record.rfc2317_cname_record.delete(
|
|
357
|
+
ptr_record.rfc2317_cname_record.delete(
|
|
358
|
+
save_zone_serial=save_zone_serial
|
|
359
|
+
)
|
|
343
360
|
|
|
344
361
|
with transaction.atomic():
|
|
345
362
|
if ptr_record is not None:
|
|
346
363
|
if ptr_record.zone.pk != ptr_zone.pk:
|
|
347
364
|
if ptr_record.rfc2317_cname_record is not None:
|
|
348
365
|
ptr_record.rfc2317_cname_record.delete()
|
|
349
|
-
ptr_record.delete()
|
|
366
|
+
ptr_record.delete(save_zone_serial=save_zone_serial)
|
|
350
367
|
ptr_record = None
|
|
351
368
|
|
|
352
369
|
else:
|
|
@@ -358,7 +375,7 @@ class Record(NetBoxModel):
|
|
|
358
375
|
ptr_record.name = ptr_name
|
|
359
376
|
ptr_record.value = ptr_value
|
|
360
377
|
ptr_record.ttl = self.ttl
|
|
361
|
-
ptr_record.save()
|
|
378
|
+
ptr_record.save(save_zone_serial=save_zone_serial)
|
|
362
379
|
|
|
363
380
|
if ptr_record is None:
|
|
364
381
|
ptr_record = Record(
|
|
@@ -369,11 +386,14 @@ class Record(NetBoxModel):
|
|
|
369
386
|
value=ptr_value,
|
|
370
387
|
managed=True,
|
|
371
388
|
)
|
|
372
|
-
ptr_record.save(
|
|
389
|
+
ptr_record.save(
|
|
390
|
+
update_rfc2317_cname=update_rfc2317_cname,
|
|
391
|
+
save_zone_serial=save_zone_serial,
|
|
392
|
+
)
|
|
373
393
|
|
|
374
394
|
self.ptr_record = ptr_record
|
|
375
395
|
|
|
376
|
-
def update_rfc2317_cname_record(self):
|
|
396
|
+
def update_rfc2317_cname_record(self, save_zone_serial=True):
|
|
377
397
|
if self.zone.rfc2317_parent_managed:
|
|
378
398
|
cname_name = dns_name.from_text(
|
|
379
399
|
ipaddress.ip_address(self.ip_address).reverse_pointer
|
|
@@ -383,7 +403,13 @@ class Record(NetBoxModel):
|
|
|
383
403
|
self.rfc2317_cname_record.name = cname_name
|
|
384
404
|
self.rfc2317_cname_record.zone = self.zone.rfc2317_parent_zone
|
|
385
405
|
self.rfc2317_cname_record.value = self.fqdn
|
|
386
|
-
self.rfc2317_cname_record.
|
|
406
|
+
self.rfc2317_cname_record.ttl = min_ttl(
|
|
407
|
+
self.rfc2317_cname_record.rfc2317_ptr_records.exclude(pk=self.pk)
|
|
408
|
+
.aggregate(Min("ttl"))
|
|
409
|
+
.get("ttl__min"),
|
|
410
|
+
self.ttl,
|
|
411
|
+
)
|
|
412
|
+
self.rfc2317_cname_record.save(save_zone_serial=save_zone_serial)
|
|
387
413
|
else:
|
|
388
414
|
rfc2317_cname_record = Record.objects.filter(
|
|
389
415
|
name=cname_name,
|
|
@@ -392,20 +418,34 @@ class Record(NetBoxModel):
|
|
|
392
418
|
managed=True,
|
|
393
419
|
value=self.fqdn,
|
|
394
420
|
).first()
|
|
395
|
-
|
|
396
|
-
|
|
421
|
+
|
|
422
|
+
if rfc2317_cname_record is not None:
|
|
423
|
+
rfc2317_cname_record.ttl = min_ttl(
|
|
424
|
+
rfc2317_cname_record.rfc2317_ptr_records.exclude(pk=self.pk)
|
|
425
|
+
.aggregate(Min("ttl"))
|
|
426
|
+
.get("ttl__min"),
|
|
427
|
+
self.ttl,
|
|
428
|
+
)
|
|
429
|
+
rfc2317_cname_record.save(
|
|
430
|
+
update_fields=["ttl"], save_zone_serial=save_zone_serial
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
else:
|
|
434
|
+
rfc2317_cname_record = Record(
|
|
397
435
|
name=cname_name,
|
|
398
436
|
type=RecordTypeChoices.CNAME,
|
|
399
437
|
zone=self.zone.rfc2317_parent_zone,
|
|
400
438
|
managed=True,
|
|
401
439
|
value=self.fqdn,
|
|
440
|
+
ttl=self.ttl,
|
|
402
441
|
)
|
|
442
|
+
rfc2317_cname_record.save(save_zone_serial=save_zone_serial)
|
|
403
443
|
|
|
404
444
|
self.rfc2317_cname_record = rfc2317_cname_record
|
|
405
445
|
|
|
406
446
|
else:
|
|
407
447
|
if self.rfc2317_cname_record is not None:
|
|
408
|
-
self.rfc2317_cname_record.delete()
|
|
448
|
+
self.rfc2317_cname_record.delete(save_zone_serial=save_zone_serial)
|
|
409
449
|
self.rfc2317_cname_record = None
|
|
410
450
|
|
|
411
451
|
def validate_name(self):
|
|
@@ -475,7 +515,7 @@ class Record(NetBoxModel):
|
|
|
475
515
|
}
|
|
476
516
|
) from None
|
|
477
517
|
|
|
478
|
-
def
|
|
518
|
+
def check_unique_record(self):
|
|
479
519
|
if not get_plugin_config("netbox_dns", "enforce_unique_records", False):
|
|
480
520
|
return
|
|
481
521
|
|
|
@@ -489,6 +529,10 @@ class Record(NetBoxModel):
|
|
|
489
529
|
value=self.value,
|
|
490
530
|
status__in=Record.ACTIVE_STATUS_LIST,
|
|
491
531
|
)
|
|
532
|
+
|
|
533
|
+
if self.pk is not None:
|
|
534
|
+
records = records.exclude(pk=self.pk)
|
|
535
|
+
|
|
492
536
|
if len(records):
|
|
493
537
|
raise ValidationError(
|
|
494
538
|
{
|
|
@@ -496,6 +540,64 @@ class Record(NetBoxModel):
|
|
|
496
540
|
}
|
|
497
541
|
) from None
|
|
498
542
|
|
|
543
|
+
def check_unique_rrset_ttl(self):
|
|
544
|
+
if self.pk is not None:
|
|
545
|
+
return
|
|
546
|
+
|
|
547
|
+
if not get_plugin_config("netbox_dns", "enforce_unique_rrset_ttl", False):
|
|
548
|
+
return
|
|
549
|
+
|
|
550
|
+
if self.type == RecordTypeChoices.PTR and self.managed:
|
|
551
|
+
return
|
|
552
|
+
|
|
553
|
+
records = (
|
|
554
|
+
Record.objects.filter(
|
|
555
|
+
zone=self.zone,
|
|
556
|
+
name=self.name,
|
|
557
|
+
type=self.type,
|
|
558
|
+
)
|
|
559
|
+
.exclude(ttl=self.ttl)
|
|
560
|
+
.exclude(type=RecordTypeChoices.PTR, managed=True)
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
if not records.exists():
|
|
564
|
+
return
|
|
565
|
+
|
|
566
|
+
conflicting_ttls = ", ".join(set(str(record.ttl) for record in records))
|
|
567
|
+
raise ValidationError(
|
|
568
|
+
{
|
|
569
|
+
"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})."
|
|
570
|
+
}
|
|
571
|
+
) from None
|
|
572
|
+
|
|
573
|
+
def update_rrset_ttl(self, ttl=None):
|
|
574
|
+
if self.pk is None:
|
|
575
|
+
return
|
|
576
|
+
|
|
577
|
+
if not get_plugin_config("netbox_dns", "enforce_unique_rrset_ttl", False):
|
|
578
|
+
return
|
|
579
|
+
|
|
580
|
+
if self.type == RecordTypeChoices.PTR and self.managed:
|
|
581
|
+
return
|
|
582
|
+
|
|
583
|
+
if ttl is None:
|
|
584
|
+
ttl = self.ttl
|
|
585
|
+
|
|
586
|
+
records = (
|
|
587
|
+
Record.objects.filter(
|
|
588
|
+
zone=self.zone,
|
|
589
|
+
name=self.name,
|
|
590
|
+
type=self.type,
|
|
591
|
+
)
|
|
592
|
+
.exclude(pk=self.pk)
|
|
593
|
+
.exclude(ttl=ttl)
|
|
594
|
+
.exclude(type=RecordTypeChoices.PTR, managed=True)
|
|
595
|
+
)
|
|
596
|
+
|
|
597
|
+
for record in records:
|
|
598
|
+
record.ttl = ttl
|
|
599
|
+
record.save(update_fields=["ttl"], update_rrset_ttl=False)
|
|
600
|
+
|
|
499
601
|
def clean_fields(self, *args, **kwargs):
|
|
500
602
|
self.type = self.type.upper()
|
|
501
603
|
super().clean_fields(*args, **kwargs)
|
|
@@ -503,7 +605,9 @@ class Record(NetBoxModel):
|
|
|
503
605
|
def clean(self, *args, **kwargs):
|
|
504
606
|
self.validate_name()
|
|
505
607
|
self.validate_value()
|
|
506
|
-
self.
|
|
608
|
+
self.check_unique_record()
|
|
609
|
+
if self.pk is None:
|
|
610
|
+
self.check_unique_rrset_ttl()
|
|
507
611
|
|
|
508
612
|
if not self.is_active:
|
|
509
613
|
return
|
|
@@ -578,14 +682,24 @@ class Record(NetBoxModel):
|
|
|
578
682
|
}
|
|
579
683
|
) from None
|
|
580
684
|
|
|
581
|
-
def save(
|
|
685
|
+
def save(
|
|
686
|
+
self,
|
|
687
|
+
*args,
|
|
688
|
+
update_rfc2317_cname=True,
|
|
689
|
+
save_zone_serial=True,
|
|
690
|
+
update_rrset_ttl=True,
|
|
691
|
+
**kwargs,
|
|
692
|
+
):
|
|
582
693
|
self.full_clean()
|
|
583
694
|
|
|
695
|
+
if self.pk is not None and update_rrset_ttl:
|
|
696
|
+
self.update_rrset_ttl()
|
|
697
|
+
|
|
584
698
|
if self.is_ptr_record:
|
|
585
699
|
if self.zone.is_rfc2317_zone:
|
|
586
700
|
self.ip_address = self.address_from_rfc2317_name
|
|
587
701
|
if update_rfc2317_cname:
|
|
588
|
-
self.update_rfc2317_cname_record()
|
|
702
|
+
self.update_rfc2317_cname_record(save_zone_serial=save_zone_serial)
|
|
589
703
|
else:
|
|
590
704
|
self.ip_address = self.address_from_name
|
|
591
705
|
|
|
@@ -595,7 +709,10 @@ class Record(NetBoxModel):
|
|
|
595
709
|
self.ip_address = None
|
|
596
710
|
|
|
597
711
|
if self.is_address_record:
|
|
598
|
-
self.update_ptr_record(
|
|
712
|
+
self.update_ptr_record(
|
|
713
|
+
update_rfc2317_cname=update_rfc2317_cname,
|
|
714
|
+
save_zone_serial=save_zone_serial,
|
|
715
|
+
)
|
|
599
716
|
elif self.ptr_record is not None:
|
|
600
717
|
self.ptr_record.delete()
|
|
601
718
|
self.ptr_record = None
|
|
@@ -604,15 +721,24 @@ class Record(NetBoxModel):
|
|
|
604
721
|
|
|
605
722
|
zone = self.zone
|
|
606
723
|
if self.type != RecordTypeChoices.SOA and zone.soa_serial_auto:
|
|
607
|
-
zone.update_serial()
|
|
724
|
+
zone.update_serial(save_zone_serial=save_zone_serial)
|
|
608
725
|
|
|
609
|
-
def delete(self, *args, **kwargs):
|
|
726
|
+
def delete(self, *args, save_zone_serial=True, **kwargs):
|
|
610
727
|
if self.rfc2317_cname_record:
|
|
611
|
-
if
|
|
612
|
-
self.rfc2317_cname_record.
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
728
|
+
if self.rfc2317_cname_record.pk:
|
|
729
|
+
if self.rfc2317_cname_record.rfc2317_ptr_records.count() == 1:
|
|
730
|
+
self.rfc2317_cname_record.delete()
|
|
731
|
+
else:
|
|
732
|
+
self.rfc2317_cname_record.ttl = (
|
|
733
|
+
self.rfc2317_cname_record.rfc2317_ptr_records.exclude(
|
|
734
|
+
pk=self.pk
|
|
735
|
+
)
|
|
736
|
+
.aggregate(Min("ttl"))
|
|
737
|
+
.get("ttl__min")
|
|
738
|
+
)
|
|
739
|
+
self.rfc2317_cname_record.save(
|
|
740
|
+
update_fields=["ttl"], save_zone_serial=save_zone_serial
|
|
741
|
+
)
|
|
616
742
|
|
|
617
743
|
if self.ptr_record:
|
|
618
744
|
self.ptr_record.delete()
|
|
@@ -621,7 +747,7 @@ class Record(NetBoxModel):
|
|
|
621
747
|
|
|
622
748
|
zone = self.zone
|
|
623
749
|
if zone.soa_serial_auto:
|
|
624
|
-
zone.update_serial()
|
|
750
|
+
zone.update_serial(save_zone_serial=save_zone_serial)
|
|
625
751
|
|
|
626
752
|
|
|
627
753
|
@register_search
|
netbox_dns/models/zone.py
CHANGED
|
@@ -249,6 +249,8 @@ class Zone(NetBoxModel):
|
|
|
249
249
|
null=True,
|
|
250
250
|
)
|
|
251
251
|
|
|
252
|
+
soa_serial_dirty = False
|
|
253
|
+
|
|
252
254
|
objects = ZoneManager()
|
|
253
255
|
|
|
254
256
|
clone_fields = [
|
|
@@ -470,11 +472,24 @@ class Zone(NetBoxModel):
|
|
|
470
472
|
|
|
471
473
|
return soa_serial
|
|
472
474
|
|
|
473
|
-
def update_serial(self):
|
|
475
|
+
def update_serial(self, save_zone_serial=True):
|
|
476
|
+
if not self.soa_serial_auto:
|
|
477
|
+
return
|
|
478
|
+
|
|
474
479
|
self.last_updated = datetime.now()
|
|
475
480
|
self.soa_serial = ceil(datetime.now().timestamp())
|
|
476
|
-
|
|
477
|
-
|
|
481
|
+
|
|
482
|
+
if save_zone_serial:
|
|
483
|
+
super().save(update_fields=["soa_serial", "last_updated"])
|
|
484
|
+
self.soa_serial_dirty = False
|
|
485
|
+
self.update_soa_record()
|
|
486
|
+
else:
|
|
487
|
+
self.soa_serial_dirty = True
|
|
488
|
+
|
|
489
|
+
def save_soa_serial(self):
|
|
490
|
+
if self.soa_serial_auto and self.soa_serial_dirty:
|
|
491
|
+
super().save(update_fields=["soa_serial", "last_updated"])
|
|
492
|
+
self.soa_serial_dirty = False
|
|
478
493
|
|
|
479
494
|
@property
|
|
480
495
|
def network_from_name(self):
|
|
@@ -503,28 +518,45 @@ class Zone(NetBoxModel):
|
|
|
503
518
|
if rfc2317_parent_zone is None:
|
|
504
519
|
self.rfc2317_parent_managed = False
|
|
505
520
|
self.rfc2317_parent_zone = None
|
|
506
|
-
self.save(
|
|
521
|
+
self.save(
|
|
522
|
+
update_fields=["rfc2317_parent_zone", "rfc2317_parent_managed"]
|
|
523
|
+
)
|
|
507
524
|
|
|
508
525
|
elif self.rfc2317_parent_zone != rfc2317_parent_zone:
|
|
509
526
|
self.rfc2317_parent_zone = rfc2317_parent_zone
|
|
510
|
-
self.save()
|
|
527
|
+
self.save(update_fields=["rfc2317_parent_zone"])
|
|
511
528
|
|
|
512
|
-
ptr_records = self.record_set.filter(
|
|
529
|
+
ptr_records = self.record_set.filter(
|
|
530
|
+
type=record.RecordTypeChoices.PTR
|
|
531
|
+
).prefetch_related("zone", "rfc2317_cname_record")
|
|
532
|
+
ptr_zones = {ptr_record.zone for ptr_record in ptr_records}
|
|
513
533
|
|
|
514
534
|
if self.rfc2317_parent_managed:
|
|
515
535
|
for ptr_record in ptr_records:
|
|
516
|
-
ptr_record.save()
|
|
536
|
+
ptr_record.save(save_zone_serial=False)
|
|
517
537
|
|
|
538
|
+
self.rfc2317_parent_zone.save_soa_serial()
|
|
539
|
+
self.rfc2317_parent_zone.update_soa_record()
|
|
518
540
|
else:
|
|
519
541
|
cname_records = {
|
|
520
542
|
ptr_record.rfc2317_cname_record
|
|
521
543
|
for ptr_record in ptr_records
|
|
522
544
|
if ptr_record.rfc2317_cname_record is not None
|
|
523
545
|
}
|
|
546
|
+
cname_zones = {cname_record.zone for cname_record in cname_records}
|
|
547
|
+
|
|
524
548
|
for ptr_record in ptr_records:
|
|
525
|
-
ptr_record.save(update_rfc2317_cname=False)
|
|
549
|
+
ptr_record.save(update_rfc2317_cname=False, save_zone_serial=False)
|
|
526
550
|
for cname_record in cname_records:
|
|
527
|
-
cname_record.delete()
|
|
551
|
+
cname_record.delete(save_zone_serial=False)
|
|
552
|
+
|
|
553
|
+
for cname_zone in cname_zones:
|
|
554
|
+
cname_zone.save_soa_serial()
|
|
555
|
+
cname_zone.update_soa_record()
|
|
556
|
+
|
|
557
|
+
for ptr_zone in ptr_zones:
|
|
558
|
+
ptr_zone.save_soa_serial()
|
|
559
|
+
ptr_zone.update_soa_record()
|
|
528
560
|
|
|
529
561
|
def clean(self, *args, **kwargs):
|
|
530
562
|
self.check_name_conflict()
|
|
@@ -643,7 +675,12 @@ class Zone(NetBoxModel):
|
|
|
643
675
|
)
|
|
644
676
|
|
|
645
677
|
for address_record in address_records:
|
|
646
|
-
address_record.save(
|
|
678
|
+
address_record.save(
|
|
679
|
+
update_fields=["ptr_record"], save_zone_serial=False
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
for zone in zones:
|
|
683
|
+
zone.save_soa_serial()
|
|
647
684
|
|
|
648
685
|
if self.arpa_network.version == 4:
|
|
649
686
|
rfc2317_child_zones = Zone.objects.filter(
|
|
@@ -674,9 +711,14 @@ class Zone(NetBoxModel):
|
|
|
674
711
|
|
|
675
712
|
for address_record in address_records:
|
|
676
713
|
address_record.save(
|
|
677
|
-
update_fields=["ptr_record"],
|
|
714
|
+
update_fields=["ptr_record"],
|
|
715
|
+
update_rfc2317_cname=False,
|
|
716
|
+
save_zone_serial=False,
|
|
678
717
|
)
|
|
679
718
|
|
|
719
|
+
for zone in zones:
|
|
720
|
+
zone.save_soa_serial()
|
|
721
|
+
|
|
680
722
|
self.update_rfc2317_parent_zone()
|
|
681
723
|
|
|
682
724
|
elif name_changed or view_changed or status_changed:
|
|
@@ -696,11 +738,14 @@ class Zone(NetBoxModel):
|
|
|
696
738
|
ip.dns_name = f'{ip.custom_field_data["ipaddress_dns_record_name"]}.{self.name}'
|
|
697
739
|
ip.save(update_fields=["dns_name"])
|
|
698
740
|
|
|
741
|
+
self.save_soa_serial()
|
|
699
742
|
self.update_soa_record()
|
|
700
743
|
|
|
701
744
|
def delete(self, *args, **kwargs):
|
|
702
745
|
with transaction.atomic():
|
|
703
|
-
address_records = self.record_set.filter(
|
|
746
|
+
address_records = self.record_set.filter(
|
|
747
|
+
ptr_record__isnull=False
|
|
748
|
+
).prefetch_related("ptr_record")
|
|
704
749
|
for address_record in address_records:
|
|
705
750
|
address_record.ptr_record.delete()
|
|
706
751
|
|
|
@@ -717,8 +762,13 @@ class Zone(NetBoxModel):
|
|
|
717
762
|
for ptr_record in ptr_records
|
|
718
763
|
if ptr_record.rfc2317_cname_record is not None
|
|
719
764
|
}
|
|
765
|
+
cname_zones = {cname_record.zone for cname_record in cname_records}
|
|
766
|
+
|
|
720
767
|
for cname_record in cname_records:
|
|
721
|
-
cname_record.delete()
|
|
768
|
+
cname_record.delete(save_zone_serial=False)
|
|
769
|
+
for cname_zone in cname_zones:
|
|
770
|
+
cname_zone.save_soa_serial()
|
|
771
|
+
cname_zone.update_soa_record()
|
|
722
772
|
|
|
723
773
|
rfc2317_child_zones = [
|
|
724
774
|
child_zone.pk for child_zone in self.rfc2317_child_zones.all()
|
|
@@ -735,11 +785,25 @@ class Zone(NetBoxModel):
|
|
|
735
785
|
|
|
736
786
|
super().delete(*args, **kwargs)
|
|
737
787
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
788
|
+
address_records = record.Record.objects.filter(
|
|
789
|
+
pk__in=update_records
|
|
790
|
+
).prefetch_related("zone")
|
|
791
|
+
|
|
792
|
+
for address_record in address_records:
|
|
793
|
+
address_record.save(save_zone_serial=False)
|
|
794
|
+
for address_zone in {address_record.zone for address_record in address_records}:
|
|
795
|
+
address_zone.save_soa_serial()
|
|
796
|
+
address_zone.update_soa_record()
|
|
797
|
+
|
|
798
|
+
rfc2317_child_zones = Zone.objects.filter(pk__in=rfc2317_child_zones)
|
|
799
|
+
if rfc2317_child_zones:
|
|
800
|
+
for child_zone in rfc2317_child_zones:
|
|
801
|
+
child_zone.update_rfc2317_parent_zone()
|
|
802
|
+
|
|
803
|
+
new_rfc2317_parent_zone = rfc2317_child_zones.first().rfc2317_parent_zone
|
|
804
|
+
if new_rfc2317_parent_zone is not None:
|
|
805
|
+
new_rfc2317_parent_zone.save_soa_serial()
|
|
806
|
+
new_rfc2317_parent_zone.update_soa_record()
|
|
743
807
|
|
|
744
808
|
|
|
745
809
|
@receiver(m2m_changed, sender=Zone.nameservers.through)
|
|
@@ -112,12 +112,6 @@
|
|
|
112
112
|
<td><a href="{% url 'ipam:ipaddress' pk=object.ipam_ip_address.pk %}">{{ object.ipam_ip_address }}</td>
|
|
113
113
|
</tr>
|
|
114
114
|
{% endif %}
|
|
115
|
-
{% if object.rfc2317_cname_record %}
|
|
116
|
-
<tr>
|
|
117
|
-
<th scope="row">RFC2317 CNAME Record</th>
|
|
118
|
-
<td><a href="{% url 'plugins:netbox_dns:record' pk=object.rfc2317_cname_record.pk %}">{{ object.rfc2317_cname_record }}</td>
|
|
119
|
-
</tr>
|
|
120
|
-
{% endif %}
|
|
121
115
|
<tr>
|
|
122
116
|
<th scope="row">Status</th>
|
|
123
117
|
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
|
@@ -131,23 +125,13 @@
|
|
|
131
125
|
</table>
|
|
132
126
|
</div>
|
|
133
127
|
</div>
|
|
134
|
-
{% if
|
|
128
|
+
{% if cname_target_table and cname_target_table.rows|length > 0 %}
|
|
135
129
|
<div class="card">
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
<th>PTR Record</th>
|
|
142
|
-
</tr>
|
|
143
|
-
{% for record in object.rfc2317_ptr_records.all %}
|
|
144
|
-
<tr>
|
|
145
|
-
<td><a href="{% url 'plugins:netbox_dns:record' pk=record.address_record.pk %}">{{ record.address_record }}</td>
|
|
146
|
-
<td><a href="{% url 'plugins:netbox_dns:record' pk=record.pk %}">{{ record }}</td>
|
|
147
|
-
</td>
|
|
148
|
-
{% endfor %}
|
|
149
|
-
</table>
|
|
150
|
-
</div>
|
|
130
|
+
{% include 'inc/panel_table.html' with table=cname_target_table heading='CNAME Targets' %}
|
|
131
|
+
</div>
|
|
132
|
+
{% elif cname_table and cname_table.rows|length > 0 %}
|
|
133
|
+
<div class="card">
|
|
134
|
+
{% include 'inc/panel_table.html' with table=cname_table heading='CNAMEs' %}
|
|
151
135
|
</div>
|
|
152
136
|
{% endif %}
|
|
153
137
|
{% if not object.managed %}
|
|
@@ -22,6 +22,10 @@
|
|
|
22
22
|
<h5 class="card-header">Contact Details</h5>
|
|
23
23
|
<div class="card-body">
|
|
24
24
|
<table class="table table-hover attr-table">
|
|
25
|
+
<tr>
|
|
26
|
+
<th scope="row">Address</th>
|
|
27
|
+
<td>{{ object.address }}</td>
|
|
28
|
+
</tr>
|
|
25
29
|
<tr>
|
|
26
30
|
<th scope="row">Referral URL</th>
|
|
27
31
|
<td><a href="{{ object.referral_url }}">{{ object.referral_url }}</a></td>
|
|
@@ -120,10 +120,17 @@
|
|
|
120
120
|
<th scope="row">Responsible</th>
|
|
121
121
|
<td>{{ object.soa_rname }}</td>
|
|
122
122
|
</tr>
|
|
123
|
+
{% if object.soa_serial_auto %}
|
|
124
|
+
<tr>
|
|
125
|
+
<th scope="row">Serial (auto-generated)</th>
|
|
126
|
+
<td>{{ object.soa_serial }}</td>
|
|
127
|
+
</tr>
|
|
128
|
+
{% else %}
|
|
123
129
|
<tr>
|
|
124
130
|
<th scope="row">Serial</th>
|
|
125
131
|
<td>{{ object.soa_serial }}</td>
|
|
126
132
|
</tr>
|
|
133
|
+
{% endif %}
|
|
127
134
|
<tr>
|
|
128
135
|
<th scope="row">Refresh</th>
|
|
129
136
|
<td>{{ object.soa_refresh }}</td>
|
netbox_dns/views/record.py
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
from dns import name as dns_name
|
|
2
2
|
|
|
3
|
+
from django.db.models import Q
|
|
4
|
+
from django.db.models.functions import Length
|
|
5
|
+
|
|
3
6
|
from netbox.views import generic
|
|
4
7
|
|
|
5
8
|
from netbox_dns.filters import RecordFilter
|
|
@@ -9,8 +12,8 @@ from netbox_dns.forms import (
|
|
|
9
12
|
RecordForm,
|
|
10
13
|
RecordBulkEditForm,
|
|
11
14
|
)
|
|
12
|
-
from netbox_dns.models import Record
|
|
13
|
-
from netbox_dns.tables import RecordTable, ManagedRecordTable
|
|
15
|
+
from netbox_dns.models import Record, RecordTypeChoices, Zone
|
|
16
|
+
from netbox_dns.tables import RecordTable, ManagedRecordTable, RelatedRecordTable
|
|
14
17
|
from netbox_dns.utilities import value_to_unicode
|
|
15
18
|
|
|
16
19
|
|
|
@@ -37,6 +40,72 @@ class ManagedRecordListView(generic.ObjectListView):
|
|
|
37
40
|
class RecordView(generic.ObjectView):
|
|
38
41
|
queryset = Record.objects.all().prefetch_related("zone", "ptr_record")
|
|
39
42
|
|
|
43
|
+
def get_value_records(self, instance):
|
|
44
|
+
value_fqdn = dns_name.from_text(instance.value_fqdn)
|
|
45
|
+
value_zone_names = [
|
|
46
|
+
value_fqdn.split(length)[1].to_text().rstrip(".")
|
|
47
|
+
for length in range(2, len(value_fqdn))
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
value_zone = (
|
|
51
|
+
Zone.objects.filter(instance.zone.view_filter, name__in=value_zone_names)
|
|
52
|
+
.order_by(Length("name").desc())
|
|
53
|
+
.first()
|
|
54
|
+
)
|
|
55
|
+
if not value_zone:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
value_name = value_fqdn.relativize(dns_name.from_text(value_zone.name))
|
|
59
|
+
cname_targets = Record.objects.filter(zone=value_zone, name=value_name)
|
|
60
|
+
|
|
61
|
+
if cname_targets:
|
|
62
|
+
return RelatedRecordTable(
|
|
63
|
+
data=cname_targets,
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
def get_cname_records(self, instance):
|
|
69
|
+
view_filter = (
|
|
70
|
+
Q(zone__view__isnull=True)
|
|
71
|
+
if instance.zone.view is None
|
|
72
|
+
else Q(zone__view=instance.zone.view)
|
|
73
|
+
)
|
|
74
|
+
cname_records = set(
|
|
75
|
+
Record.objects.filter(
|
|
76
|
+
view_filter, value=instance.fqdn, type=RecordTypeChoices.CNAME
|
|
77
|
+
)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
fqdn = dns_name.from_text(instance.fqdn)
|
|
81
|
+
parent_zone_names = [
|
|
82
|
+
fqdn.split(length)[1].to_text().rstrip(".")
|
|
83
|
+
for length in range(1, len(fqdn))
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
parent_zones = Zone.objects.filter(
|
|
87
|
+
instance.zone.view_filter, name__in=parent_zone_names
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
for parent_zone in parent_zones:
|
|
91
|
+
parent_cname_records = Record.objects.filter(
|
|
92
|
+
view_filter, type=RecordTypeChoices.CNAME, zone=parent_zone
|
|
93
|
+
)
|
|
94
|
+
cname_records = cname_records.union(
|
|
95
|
+
set(
|
|
96
|
+
record
|
|
97
|
+
for record in parent_cname_records
|
|
98
|
+
if record.value_fqdn == instance.fqdn
|
|
99
|
+
)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if cname_records:
|
|
103
|
+
return RelatedRecordTable(
|
|
104
|
+
data=cname_records,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return None
|
|
108
|
+
|
|
40
109
|
def get_extra_context(self, request, instance):
|
|
41
110
|
context = {}
|
|
42
111
|
|
|
@@ -48,6 +117,11 @@ class RecordView(generic.ObjectView):
|
|
|
48
117
|
if instance.value != unicode_value:
|
|
49
118
|
context["unicode_value"] = unicode_value
|
|
50
119
|
|
|
120
|
+
if instance.type == RecordTypeChoices.CNAME:
|
|
121
|
+
context["cname_target_table"] = self.get_value_records(instance)
|
|
122
|
+
else:
|
|
123
|
+
context["cname_table"] = self.get_cname_records(instance)
|
|
124
|
+
|
|
51
125
|
return context
|
|
52
126
|
|
|
53
127
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: netbox-plugin-dns
|
|
3
|
-
Version: 0.22.
|
|
3
|
+
Version: 0.22.6
|
|
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,6 +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.8,<4.0
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
13
|
Classifier: Programming Language :: Python :: 3
|
|
13
14
|
Classifier: Programming Language :: Python :: 3.8
|
|
@@ -19,16 +20,15 @@ Requires-Dist: dnspython (>=2.2.1,<3.0.0)
|
|
|
19
20
|
Project-URL: Repository, https://github.com/peteeckel/netbox-plugin-dns
|
|
20
21
|
Description-Content-Type: text/markdown
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
<p align="center"><i>NetBox DNS is a NetBox plugin for managing DNS views, zones, name servers and records.</i></p>
|
|
23
|
+
# NetBox DNS
|
|
24
|
+
The NetBox DNS plugin enables NetBox to manage operational DNS data such as name servers, zones, records and views, as well as registration data for domains. It can automate tasks like creating PTR records, generating zone serial numbers, NS and SOA records, as well as validate names and values values for resource records to ensure zone data is consistent, current and conforming to the relevant RFCs.
|
|
25
25
|
|
|
26
26
|
<div align="center">
|
|
27
27
|
<a href="https://pypi.org/project/netbox-plugin-dns/"><img src="https://img.shields.io/pypi/v/netbox-plugin-dns" alt="PyPi"/></a>
|
|
28
|
-
<a href="https://github.com/peteeckel/netbox-plugin-dns/stargazers"><img src="https://img.shields.io/github/stars/peteeckel/netbox-plugin-dns" alt="Stars Badge"/></a>
|
|
29
|
-
<a href="https://github.com/peteeckel/netbox-plugin-dns/network/members"><img src="https://img.shields.io/github/forks/peteeckel/netbox-plugin-dns" alt="Forks Badge"/></a>
|
|
30
|
-
<a href="https://github.com/peteeckel/netbox-plugin-dns/pulls"><img src="https://img.shields.io/github/issues-pr/peteeckel/netbox-plugin-dns" alt="Pull Requests Badge"/></a>
|
|
28
|
+
<a href="https://github.com/peteeckel/netbox-plugin-dns/stargazers"><img src="https://img.shields.io/github/stars/peteeckel/netbox-plugin-dns?style=flat" alt="Stars Badge"/></a>
|
|
29
|
+
<a href="https://github.com/peteeckel/netbox-plugin-dns/network/members"><img src="https://img.shields.io/github/forks/peteeckel/netbox-plugin-dns?style=flat" alt="Forks Badge"/></a>
|
|
31
30
|
<a href="https://github.com/peteeckel/netbox-plugin-dns/issues"><img src="https://img.shields.io/github/issues/peteeckel/netbox-plugin-dns" alt="Issues Badge"/></a>
|
|
31
|
+
<a href="https://github.com/peteeckel/netbox-plugin-dns/pulls"><img src="https://img.shields.io/github/issues-pr/peteeckel/netbox-plugin-dns" alt="Pull Requests Badge"/></a>
|
|
32
32
|
<a href="https://github.com/peteeckel/netbox-plugin-dns/graphs/contributors"><img alt="GitHub contributors" src="https://img.shields.io/github/contributors/peteeckel/netbox-plugin-dns?color=2b9348"></a>
|
|
33
33
|
<a href="https://github.com/peteeckel/netbox-plugin-dns/blob/master/LICENSE"><img src="https://img.shields.io/github/license/peteeckel/netbox-plugin-dns?color=2b9348" alt="License Badge"/></a>
|
|
34
34
|
<a href="https://pepy.tech/project/netbox-plugin-dns"><img alt="Downloads" src="https://static.pepy.tech/badge/netbox-plugin-dns"></a>
|
|
@@ -40,10 +40,10 @@ Description-Content-Type: text/markdown
|
|
|
40
40
|
|
|
41
41
|
* Manage name servers, zones and records
|
|
42
42
|
* Automatically generate SOA and NS records for zones
|
|
43
|
-
* Automatically create and update PTR records for IPv4 and IPv6 records
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
43
|
+
* Automatically create and update PTR records for IPv4 and IPv6 address records
|
|
44
|
+
* Organize DNS zones in views for split horizon DNS and multi-site deployments
|
|
45
|
+
* Manage domain registrar and registrant information for domains related to zones
|
|
46
|
+
* Manage RFC2317 reverse zones for IPv4 prefixes with a network mask length longer than 24 bits
|
|
47
47
|
|
|
48
48
|
NetBox DNS is using the standardized NetBox plugin interface, so it also takes advantage of the NetBox tagging and change log features.
|
|
49
49
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
netbox_dns/__init__.py,sha256=
|
|
1
|
+
netbox_dns/__init__.py,sha256=_MWBhj_SFYmwsvZRS4rBlGcGT5kgLacAUhdzHZ1gZ5I,1287
|
|
2
2
|
netbox_dns/api/nested_serializers.py,sha256=XB7bcCjVMPYrumJWgRicj06PukQ2UCBjdr84AIUJuVQ,3291
|
|
3
|
-
netbox_dns/api/serializers.py,sha256=
|
|
3
|
+
netbox_dns/api/serializers.py,sha256=H5Vm1O6C4SsxHVzdooYiu5ak3ykxXo-L1mT6ISWVRV8,8183
|
|
4
4
|
netbox_dns/api/urls.py,sha256=R9VmmWtdrjvr35i5d_SfZK2lGn6JzmPuWEKTQlZ8MJo,575
|
|
5
5
|
netbox_dns/api/views.py,sha256=DjovvTfS4F2Fq2Ahea6f4LBJiWr01ajk-wSZHNTya5I,3527
|
|
6
6
|
netbox_dns/apps.py,sha256=JCW5eS-AQBUubDJve1DjP-IRFKTFGQh1NLGWzJpC5MI,151
|
|
@@ -12,16 +12,16 @@ netbox_dns/filters/__init__.py,sha256=Aw8HrCTjaJfu5JSwJsQRHfOUz4zKwAmZNByT9q6BrF
|
|
|
12
12
|
netbox_dns/filters/contact.py,sha256=_onZ6G2KKgfvm9Emg_kng2RPETRMfbTyIR8Tvs9d2YM,1027
|
|
13
13
|
netbox_dns/filters/nameserver.py,sha256=sDKsrluSmUZ0aTw_wagYJAh1g5fKzyzHXbJu6DaK1b8,522
|
|
14
14
|
netbox_dns/filters/record.py,sha256=DH1x4bNkBAN_gVNklAjorcgHoJmH_AXEf1sBNvJS6Kc,1691
|
|
15
|
-
netbox_dns/filters/registrar.py,sha256=
|
|
15
|
+
netbox_dns/filters/registrar.py,sha256=8UXJXUb0XmkPyPQ2oACAhzhmZtAmvM2c4hTWMDLuBEo,911
|
|
16
16
|
netbox_dns/filters/view.py,sha256=Sji4DKy0VKk8BLEdk8xCe8su3rUBtXeJUsUAee0IsOs,497
|
|
17
17
|
netbox_dns/filters/zone.py,sha256=gNKTLll0nvdkzjEVl5c4O1hncU13IFVKn61iXThLYJA,3556
|
|
18
18
|
netbox_dns/forms/__init__.py,sha256=Aw8HrCTjaJfu5JSwJsQRHfOUz4zKwAmZNByT9q6BrFU,136
|
|
19
19
|
netbox_dns/forms/contact.py,sha256=Z8llYOpcGns8ZerOLsldpY836pfjO7427rmAZ4T8Kyg,4492
|
|
20
20
|
netbox_dns/forms/nameserver.py,sha256=LUYV_tna667flgPdQevDFniMdXZUYRUG8iqCx7HKdxQ,2027
|
|
21
21
|
netbox_dns/forms/record.py,sha256=TAQ-rZ55lmWE_piogiE5TwTv1v4r5T2yZpNT8K7GrPU,6171
|
|
22
|
-
netbox_dns/forms/registrar.py,sha256=
|
|
22
|
+
netbox_dns/forms/registrar.py,sha256=Vkw8cafXQRDHf7K7OFPn62R-_BzjNOofnhLmL2Hw7mg,2636
|
|
23
23
|
netbox_dns/forms/view.py,sha256=KEWlUo8-oXP_QsqT5fW_UK-ZoX9g-f4CqRnJ_P6LHco,1627
|
|
24
|
-
netbox_dns/forms/zone.py,sha256=
|
|
24
|
+
netbox_dns/forms/zone.py,sha256=Y9KfeSCzFrJU1LxQjxPcLANu3NEcaHjvxnyMqgna_FY,21702
|
|
25
25
|
netbox_dns/graphql/__init__.py,sha256=v3lLvVoP-JQbEZ1NY49u1TNqvanF-4TqOtjHXrbsTvU,811
|
|
26
26
|
netbox_dns/graphql/contact.py,sha256=2iyuvCxG09CLDUMhan5vR3uFZxCVzKNZBrotr_zoUVY,497
|
|
27
27
|
netbox_dns/graphql/nameserver.py,sha256=mlQw1Po_Ax_fjyyXVBetyxlFLrCqmptYDgOspZvYtP4,527
|
|
@@ -31,6 +31,7 @@ netbox_dns/graphql/schema.py,sha256=D_dDusogaI55tmGx_dr1owsgzXS5fd2S-kPZ7xcXxs8,
|
|
|
31
31
|
netbox_dns/graphql/view.py,sha256=S_61hYlQCtPQen1lI1UQs38UBWKQTaWfUxzlbpO07zA,467
|
|
32
32
|
netbox_dns/graphql/zone.py,sha256=QDBxocezhLSHBGDV4RJnmarBfOsiUTeE9KzBGJ3gJi8,467
|
|
33
33
|
netbox_dns/management/commands/cleanup_database.py,sha256=eggyMZrRg--cXDQJ-boofHboG1gJTs8j-oldhn6EuCo,6050
|
|
34
|
+
netbox_dns/management/commands/cleanup_rrset_ttl.py,sha256=gCvwH6hGw32YJtcy_pEWIP29PSRMW23-qzB8SHEdixQ,2077
|
|
34
35
|
netbox_dns/management/commands/setup_coupling.py,sha256=Yn1nffPR7fBgB6WWBdDqdnp3k8z1QK8k6qi9xbx4U6Y,4580
|
|
35
36
|
netbox_dns/management/commands/update_soa.py,sha256=qvlApMngTVpauj0CU0yeOy9r3lxxDciKorMxFsyvQhs,661
|
|
36
37
|
netbox_dns/migrations/0001_initial.py,sha256=R9FbIQ7nO1ROb12NL8YQDkFQuP1r6-TtMcPwg4rwjus,4153
|
|
@@ -66,10 +67,10 @@ netbox_dns/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
|
66
67
|
netbox_dns/models/__init__.py,sha256=Q7UIEe2vGh18AZN4er6CykciwXPQGgUq0L-9718wZqU,182
|
|
67
68
|
netbox_dns/models/contact.py,sha256=Mzj4SR3fczOE96etWjObJElk0RVQetNuXZVtm8sS1h8,2849
|
|
68
69
|
netbox_dns/models/nameserver.py,sha256=SjKUbMRNE3TSDzmxbKFR9b4AfYPS0UPj0QeO7XpwNRk,2941
|
|
69
|
-
netbox_dns/models/record.py,sha256=
|
|
70
|
+
netbox_dns/models/record.py,sha256=KbfSl9Np2kV_qozUB9zgoir3cwutYLvlxPa2q9X-9Jk,23896
|
|
70
71
|
netbox_dns/models/registrar.py,sha256=ByKHeqH5KfswqOLjya8DZExJ1omSKFHMfCjIIYfnwTo,1416
|
|
71
72
|
netbox_dns/models/view.py,sha256=ljs3Q2xQR63ZOSyja5H7DEdFbm7MX2ZjlR6uNVrAsVo,920
|
|
72
|
-
netbox_dns/models/zone.py,sha256=
|
|
73
|
+
netbox_dns/models/zone.py,sha256=Ek7Jui3AldNHVAHowbQhC47QoXv0IgkhLzFziN14UUI,26333
|
|
73
74
|
netbox_dns/navigation.py,sha256=IutEr_TcPgDGzqTT1ZzV4IUABLSOFU9v364BfpCqbro,4587
|
|
74
75
|
netbox_dns/signals/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
75
76
|
netbox_dns/signals/ipam_coupling.py,sha256=xbb37_77ZqtgT3mKXZp18QRduqZuiAuYGPk8R9d7SVc,5727
|
|
@@ -85,8 +86,8 @@ netbox_dns/templates/netbox_dns/contact.html,sha256=3qHGChgLYfqUgO3_z9jQ-lFIGS0Z
|
|
|
85
86
|
netbox_dns/templates/netbox_dns/nameserver.html,sha256=3AJVbsuhKg4Jy74rlvwrGSHd_IoDRdLT_XuK4EH3Djg,1623
|
|
86
87
|
netbox_dns/templates/netbox_dns/record/managed.html,sha256=G6LPG1koUGuzUiwYdv1okdVa4sKaofiQegDBnsFL0kA,89
|
|
87
88
|
netbox_dns/templates/netbox_dns/record/related.html,sha256=Aqor8uGcuHQTHjlX-Xmni2Yp4N7lOBrMOqQiszrQOC0,742
|
|
88
|
-
netbox_dns/templates/netbox_dns/record.html,sha256=
|
|
89
|
-
netbox_dns/templates/netbox_dns/registrar.html,sha256=
|
|
89
|
+
netbox_dns/templates/netbox_dns/record.html,sha256=jq1kueOd5EN1myZt8RbXl4rjoTuxWezBnVv36i74QC8,5835
|
|
90
|
+
netbox_dns/templates/netbox_dns/registrar.html,sha256=rSShbH68nP0r8EUHn0-TZOsUj6pg7hmfvM7h2tqouUA,2130
|
|
90
91
|
netbox_dns/templates/netbox_dns/related_dns_objects.html,sha256=KSzlnw1cStrJa3poKkwrt_ycIH0oH0STWIHRNy3ks4g,806
|
|
91
92
|
netbox_dns/templates/netbox_dns/view.html,sha256=_lDjd2xY3upfGpNpemXXMzgsaKZlX3-PzPPAdYkIjvs,1350
|
|
92
93
|
netbox_dns/templates/netbox_dns/zone/base.html,sha256=pucf_b7iGg4hCXqwIbR0_WtEQdeH2LQtTCRTSoeBhFc,460
|
|
@@ -95,7 +96,7 @@ netbox_dns/templates/netbox_dns/zone/managed_record.html,sha256=5P85eJuQOB7omih2
|
|
|
95
96
|
netbox_dns/templates/netbox_dns/zone/record.html,sha256=1oIRGXOZAjwmTMkTgArfKyVrmL54Sh_IN7IAF3qYEKM,2218
|
|
96
97
|
netbox_dns/templates/netbox_dns/zone/registration.html,sha256=3R5uqLXZxIJkp9_LVnTTgTV61mITfDPDDNofV7s4d1k,1283
|
|
97
98
|
netbox_dns/templates/netbox_dns/zone/rfc2317_child_zone.html,sha256=kxIrhn0gghb4l1eZhpsICdfkSYegqSXR9zFIWiZLypA,527
|
|
98
|
-
netbox_dns/templates/netbox_dns/zone.html,sha256=
|
|
99
|
+
netbox_dns/templates/netbox_dns/zone.html,sha256=_lQhr00_V8ThadlTuTm5zPkcvyjYQxxYGHQsfTOgDCs,7187
|
|
99
100
|
netbox_dns/urls.py,sha256=NxqvnRxLM6VwUBTY6KA8u-PRjNklmBai7gy6NXc-1xo,9172
|
|
100
101
|
netbox_dns/utilities/__init__.py,sha256=8DqDx-99kejykwaIt_29W5KgY8RvWBOk6526vrf22UQ,1967
|
|
101
102
|
netbox_dns/utilities/ipam_coupling.py,sha256=LhdFafF4OAOU8rgROOqY5Dt7XDT_SUZP2JuIqCQYVqw,3573
|
|
@@ -105,11 +106,11 @@ netbox_dns/validators/rfc2317.py,sha256=bgqDwdqwpf5d1mt24Wrr8z8t2IYbt5RovNr6l2nv
|
|
|
105
106
|
netbox_dns/views/__init__.py,sha256=Aw8HrCTjaJfu5JSwJsQRHfOUz4zKwAmZNByT9q6BrFU,136
|
|
106
107
|
netbox_dns/views/contact.py,sha256=6-oCfK98-submcUTmi0ejw7QBscNn3S9bnS0oUTXOaY,2235
|
|
107
108
|
netbox_dns/views/nameserver.py,sha256=Wa8CQ19P5uPNLMIYkj_U82wmwdp5gZoBWZnOR4ZExa0,2990
|
|
108
|
-
netbox_dns/views/record.py,sha256=
|
|
109
|
+
netbox_dns/views/record.py,sha256=p6TOV2dGg5uNPtk2pSqLzMRXpgC-fazpwYe2mIRSTt8,4896
|
|
109
110
|
netbox_dns/views/registrar.py,sha256=aznSKt1L5tILMLGgcZiBR7u7B8rNl-jM1B2-N0fTeK8,2072
|
|
110
111
|
netbox_dns/views/view.py,sha256=uUvtlNEh5MYoEALvWWaCOqj_Zj8dpGOL2PUyg-UPfEA,1895
|
|
111
112
|
netbox_dns/views/zone.py,sha256=SyttTAgrPPzf1jIT1B4RexCLdXYjSmPIZsefO_zog1Q,4587
|
|
112
|
-
netbox_plugin_dns-0.22.
|
|
113
|
-
netbox_plugin_dns-0.22.
|
|
114
|
-
netbox_plugin_dns-0.22.
|
|
115
|
-
netbox_plugin_dns-0.22.
|
|
113
|
+
netbox_plugin_dns-0.22.6.dist-info/LICENSE,sha256=tziMJKpkMbySr09L6bIwsu7Ca9ICoqpMO3yAXgEMQA4,1076
|
|
114
|
+
netbox_plugin_dns-0.22.6.dist-info/METADATA,sha256=v4cYHLNbhWBpdmkaefeFSQbmIqg5TxJfeI4SJpUsq4M,4572
|
|
115
|
+
netbox_plugin_dns-0.22.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
116
|
+
netbox_plugin_dns-0.22.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|