netbox-plugin-dns 1.0.7__py3-none-any.whl → 1.1.0__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 +23 -4
- netbox_dns/api/serializers.py +2 -1
- netbox_dns/api/serializers_/prefix.py +18 -0
- netbox_dns/api/serializers_/{contact.py → registration_contact.py} +5 -5
- netbox_dns/api/serializers_/view.py +34 -2
- netbox_dns/api/serializers_/zone.py +5 -5
- netbox_dns/api/serializers_/zone_template.py +5 -5
- netbox_dns/api/urls.py +5 -2
- netbox_dns/api/views.py +17 -7
- netbox_dns/fields/__init__.py +1 -0
- netbox_dns/fields/ipam.py +15 -0
- netbox_dns/filtersets/__init__.py +1 -1
- netbox_dns/filtersets/{contact.py → registration_contact.py} +4 -4
- netbox_dns/filtersets/view.py +16 -0
- netbox_dns/filtersets/zone.py +15 -15
- netbox_dns/filtersets/zone_template.py +15 -15
- netbox_dns/forms/__init__.py +1 -1
- netbox_dns/forms/{contact.py → registration_contact.py} +16 -16
- netbox_dns/forms/view.py +204 -4
- netbox_dns/forms/zone.py +15 -18
- netbox_dns/forms/zone_template.py +13 -13
- netbox_dns/graphql/__init__.py +2 -2
- netbox_dns/graphql/filters.py +5 -5
- netbox_dns/graphql/schema.py +9 -5
- netbox_dns/graphql/types.py +41 -12
- netbox_dns/management/commands/rebuild_dnssync.py +18 -0
- netbox_dns/management/commands/setup_dnssync.py +140 -0
- netbox_dns/migrations/0008_view_prefixes.py +18 -0
- netbox_dns/migrations/0009_rename_contact_registrationcontact.py +27 -0
- netbox_dns/models/__init__.py +1 -3
- netbox_dns/models/record.py +139 -20
- netbox_dns/models/{contact.py → registration_contact.py} +8 -8
- netbox_dns/models/view.py +5 -0
- netbox_dns/models/zone.py +66 -30
- netbox_dns/models/zone_template.py +4 -4
- netbox_dns/navigation.py +7 -7
- netbox_dns/signals/ipam_dnssync.py +224 -0
- netbox_dns/tables/__init__.py +1 -1
- netbox_dns/tables/ipam_dnssync.py +11 -0
- netbox_dns/tables/record.py +33 -0
- netbox_dns/tables/{contact.py → registration_contact.py} +5 -5
- netbox_dns/tables/view.py +24 -2
- netbox_dns/template_content.py +41 -40
- netbox_dns/templates/netbox_dns/record.html +6 -6
- netbox_dns/templates/netbox_dns/{contact.html → registrationcontact.html} +1 -1
- 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/related.html +17 -0
- netbox_dns/templates/netbox_dns/view.html +25 -0
- netbox_dns/urls/__init__.py +2 -2
- netbox_dns/urls/registration_contact.py +60 -0
- netbox_dns/urls/view.py +6 -0
- netbox_dns/utilities/__init__.py +2 -74
- netbox_dns/utilities/conversions.py +83 -0
- netbox_dns/utilities/ipam_dnssync.py +295 -0
- netbox_dns/views/__init__.py +1 -1
- netbox_dns/views/record.py +3 -5
- netbox_dns/views/registration_contact.py +94 -0
- netbox_dns/views/view.py +26 -1
- {netbox_plugin_dns-1.0.7.dist-info → netbox_plugin_dns-1.1.0.dist-info}/METADATA +2 -1
- {netbox_plugin_dns-1.0.7.dist-info → netbox_plugin_dns-1.1.0.dist-info}/RECORD +63 -54
- netbox_dns/management/commands/setup_coupling.py +0 -109
- netbox_dns/signals/ipam_coupling.py +0 -168
- netbox_dns/templates/netbox_dns/related_dns_objects.html +0 -21
- netbox_dns/urls/contact.py +0 -29
- netbox_dns/utilities/ipam_coupling.py +0 -112
- netbox_dns/views/contact.py +0 -94
- {netbox_plugin_dns-1.0.7.dist-info → netbox_plugin_dns-1.1.0.dist-info}/LICENSE +0 -0
- {netbox_plugin_dns-1.0.7.dist-info → netbox_plugin_dns-1.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from django.core.management.base import BaseCommand
|
|
2
|
+
|
|
3
|
+
from ipam.models import IPAddress
|
|
4
|
+
|
|
5
|
+
from netbox_dns.utilities import update_dns_records
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Command(BaseCommand):
|
|
9
|
+
help = "Rebuild DNSsync relationships between IP addresses and records"
|
|
10
|
+
|
|
11
|
+
def handle(self, *model_names, **options):
|
|
12
|
+
ip_addresses = IPAddress.objects.all()
|
|
13
|
+
for ip_address in ip_addresses:
|
|
14
|
+
if options.get("verbosity") >= 2:
|
|
15
|
+
self.stdout.write(
|
|
16
|
+
f"Updating DNS records for IP Address {ip_address}, VRF {ip_address.vrf}"
|
|
17
|
+
)
|
|
18
|
+
update_dns_records(ip_address)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from django.core.management.base import BaseCommand
|
|
2
|
+
|
|
3
|
+
from core.models import ObjectType
|
|
4
|
+
from extras.models import CustomField
|
|
5
|
+
from extras.choices import CustomFieldTypeChoices
|
|
6
|
+
from ipam.models import IPAddress
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Command(BaseCommand):
|
|
10
|
+
help = "Setup IPAddress custom fields for IPAM DNSsync"
|
|
11
|
+
|
|
12
|
+
def add_arguments(self, parser):
|
|
13
|
+
parser.add_argument(
|
|
14
|
+
"--remove", action="store_true", default=False, help="Remove custom fields"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
def handle(self, *model_names, **options):
|
|
18
|
+
ipaddress_object_type = ObjectType.objects.get_for_model(IPAddress)
|
|
19
|
+
|
|
20
|
+
if options.get("remove"):
|
|
21
|
+
if options.get("verbosity"):
|
|
22
|
+
self.stdout.write("Trying to remove IPAM DNSsync custom fields")
|
|
23
|
+
for cf in (
|
|
24
|
+
"ipaddress_dns_disabled",
|
|
25
|
+
"ipaddress_dns_record_ttl",
|
|
26
|
+
"ipaddress_dns_record_disable_ptr",
|
|
27
|
+
):
|
|
28
|
+
try:
|
|
29
|
+
CustomField.objects.get(
|
|
30
|
+
name=cf, object_types=ipaddress_object_type
|
|
31
|
+
).delete()
|
|
32
|
+
if options.get("verbosity"):
|
|
33
|
+
self.stdout.write(f"Removed custom field '{cf}'")
|
|
34
|
+
except CustomField.DoesNotExist:
|
|
35
|
+
pass
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
# +
|
|
39
|
+
# Remove pre-existing IPAM Coupling custom fields
|
|
40
|
+
# -
|
|
41
|
+
if options.get("verbosity") >= 2:
|
|
42
|
+
self.stdout.write("Trying to remove obsolete IPAM Coupling custom fields")
|
|
43
|
+
for cf in (
|
|
44
|
+
"ipaddress_dns_record_name",
|
|
45
|
+
"ipaddress_dns_zone_id",
|
|
46
|
+
):
|
|
47
|
+
try:
|
|
48
|
+
CustomField.objects.get(
|
|
49
|
+
name=cf, object_types=ipaddress_object_type
|
|
50
|
+
).delete()
|
|
51
|
+
if options.get("verbosity"):
|
|
52
|
+
self.stdout.write(f"Removed custom field '{cf}'")
|
|
53
|
+
except CustomField.DoesNotExist:
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
if options.get("verbosity") >= 2:
|
|
57
|
+
self.stdout.write("Creating IPAM DNSsync custom fields")
|
|
58
|
+
|
|
59
|
+
if not CustomField.objects.filter(
|
|
60
|
+
name="ipaddress_dns_disabled",
|
|
61
|
+
type=CustomFieldTypeChoices.TYPE_BOOLEAN,
|
|
62
|
+
object_types=ipaddress_object_type,
|
|
63
|
+
).exists():
|
|
64
|
+
cf_dnssync_disabled = CustomField.objects.create(
|
|
65
|
+
name="ipaddress_dns_disabled",
|
|
66
|
+
label="Disable DNSsync",
|
|
67
|
+
description="Disable DNS address and pointer record generation for this address",
|
|
68
|
+
type=CustomFieldTypeChoices.TYPE_BOOLEAN,
|
|
69
|
+
required=False,
|
|
70
|
+
default=False,
|
|
71
|
+
group_name="DNSsync",
|
|
72
|
+
is_cloneable=True,
|
|
73
|
+
weight=100,
|
|
74
|
+
)
|
|
75
|
+
cf_dnssync_disabled.object_types.set([ipaddress_object_type])
|
|
76
|
+
if options.get("verbosity"):
|
|
77
|
+
self.stdout.write("Created custom field 'ipaddress_dns_disabled'")
|
|
78
|
+
|
|
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 != "DNSsync":
|
|
86
|
+
cf_ttl.group_name = "DNSsync"
|
|
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:
|
|
92
|
+
cf_ttl = CustomField.objects.create(
|
|
93
|
+
name="ipaddress_dns_record_ttl",
|
|
94
|
+
description="TTL for DNS records created for this address",
|
|
95
|
+
label="TTL",
|
|
96
|
+
type=CustomFieldTypeChoices.TYPE_INTEGER,
|
|
97
|
+
validation_minimum=0,
|
|
98
|
+
validation_maximum=2147483647,
|
|
99
|
+
required=False,
|
|
100
|
+
group_name="DNSsync",
|
|
101
|
+
is_cloneable=True,
|
|
102
|
+
weight=200,
|
|
103
|
+
)
|
|
104
|
+
cf_ttl.object_types.set([ipaddress_object_type])
|
|
105
|
+
if options.get("verbosity"):
|
|
106
|
+
self.stdout.write("Created custom field 'ipaddress_dns_record_ttl'")
|
|
107
|
+
|
|
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 != "DNSsync":
|
|
115
|
+
cf_disable_ptr.group_name = "DNSsync"
|
|
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:
|
|
125
|
+
cf_disable_ptr = CustomField.objects.create(
|
|
126
|
+
name="ipaddress_dns_record_disable_ptr",
|
|
127
|
+
description="Disable DNS PTR record generation for this address",
|
|
128
|
+
label="Disable PTR",
|
|
129
|
+
type=CustomFieldTypeChoices.TYPE_BOOLEAN,
|
|
130
|
+
required=False,
|
|
131
|
+
default=False,
|
|
132
|
+
group_name="DNSsync",
|
|
133
|
+
is_cloneable=True,
|
|
134
|
+
weight=300,
|
|
135
|
+
)
|
|
136
|
+
cf_disable_ptr.object_types.set([ipaddress_object_type])
|
|
137
|
+
if options.get("verbosity"):
|
|
138
|
+
self.stdout.write(
|
|
139
|
+
"Created custom field 'ipaddress_dns_record_disable_ptr'"
|
|
140
|
+
)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from django.db import migrations, models
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Migration(migrations.Migration):
|
|
5
|
+
dependencies = [
|
|
6
|
+
("ipam", "0067_ipaddress_index_host"),
|
|
7
|
+
("netbox_dns", "0007_alter_ordering_options"),
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
operations = [
|
|
11
|
+
migrations.AddField(
|
|
12
|
+
model_name="view",
|
|
13
|
+
name="prefixes",
|
|
14
|
+
field=models.ManyToManyField(
|
|
15
|
+
blank=True, related_name="netbox_dns_views", to="ipam.prefix"
|
|
16
|
+
),
|
|
17
|
+
),
|
|
18
|
+
]
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from django.db import migrations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Migration(migrations.Migration):
|
|
5
|
+
|
|
6
|
+
dependencies = [
|
|
7
|
+
("netbox_dns", "0008_view_prefixes"),
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
operations = [
|
|
11
|
+
migrations.RenameModel(
|
|
12
|
+
old_name="Contact",
|
|
13
|
+
new_name="RegistrationContact",
|
|
14
|
+
),
|
|
15
|
+
migrations.RunSQL(
|
|
16
|
+
"ALTER TABLE netbox_dns_contact_id_seq RENAME TO netbox_dns_registrationcontact_id_seq"
|
|
17
|
+
),
|
|
18
|
+
migrations.RunSQL(
|
|
19
|
+
"ALTER INDEX netbox_dns_contact_pkey RENAME TO netbox_dns_registrationcontact_pkey"
|
|
20
|
+
),
|
|
21
|
+
migrations.RunSQL(
|
|
22
|
+
"ALTER INDEX netbox_dns_contact_contact_id_50e9d89d_like RENAME TO netbox_dns_registrationcontact_contact_id_6ff98464_like"
|
|
23
|
+
),
|
|
24
|
+
migrations.RunSQL(
|
|
25
|
+
"ALTER INDEX netbox_dns_contact_contact_id_key RENAME TO netbox_dns_contact_registrationcontact_id_key"
|
|
26
|
+
),
|
|
27
|
+
]
|
netbox_dns/models/__init__.py
CHANGED
|
@@ -2,7 +2,7 @@ from .zone import *
|
|
|
2
2
|
from .nameserver import *
|
|
3
3
|
from .record import *
|
|
4
4
|
from .view import *
|
|
5
|
-
from .
|
|
5
|
+
from .registration_contact import *
|
|
6
6
|
from .registrar import *
|
|
7
7
|
from .zone_template import *
|
|
8
8
|
from .record_template import *
|
|
@@ -11,5 +11,3 @@ from .record_template import *
|
|
|
11
11
|
# Backwards compatibility fix, will be removed in version 1.1
|
|
12
12
|
# -
|
|
13
13
|
from netbox_dns.choices import *
|
|
14
|
-
|
|
15
|
-
from netbox_dns.signals import ipam_coupling
|
netbox_dns/models/record.py
CHANGED
|
@@ -7,6 +7,7 @@ from django.core.exceptions import ValidationError
|
|
|
7
7
|
from django.db import transaction, models
|
|
8
8
|
from django.db.models import Q, ExpressionWrapper, BooleanField, Min
|
|
9
9
|
from django.urls import reverse
|
|
10
|
+
from django.conf import settings
|
|
10
11
|
|
|
11
12
|
from netbox.models import NetBoxModel
|
|
12
13
|
from netbox.models.features import ContactsMixin
|
|
@@ -36,6 +37,43 @@ def min_ttl(*ttl_list):
|
|
|
36
37
|
return min((ttl for ttl in ttl_list if ttl is not None), default=None)
|
|
37
38
|
|
|
38
39
|
|
|
40
|
+
def record_data_from_ip_address(ip_address, zone):
|
|
41
|
+
cf_data = ip_address.custom_field_data
|
|
42
|
+
|
|
43
|
+
if cf_data.get("ipaddress_dns_disabled"):
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
data = {
|
|
47
|
+
"name": (
|
|
48
|
+
dns_name.from_text(ip_address.dns_name)
|
|
49
|
+
.relativize(dns_name.from_text(zone.name))
|
|
50
|
+
.to_text()
|
|
51
|
+
),
|
|
52
|
+
"type": (
|
|
53
|
+
RecordTypeChoices.A
|
|
54
|
+
if ip_address.address.version == 4
|
|
55
|
+
else RecordTypeChoices.AAAA
|
|
56
|
+
),
|
|
57
|
+
"value": str(ip_address.address.ip),
|
|
58
|
+
"status": (
|
|
59
|
+
RecordStatusChoices.STATUS_ACTIVE
|
|
60
|
+
if ip_address.status
|
|
61
|
+
in settings.PLUGINS_CONFIG["netbox_dns"].get(
|
|
62
|
+
"dnssync_ipaddress_active_status", []
|
|
63
|
+
)
|
|
64
|
+
else RecordStatusChoices.STATUS_INACTIVE
|
|
65
|
+
),
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if "ipaddress_dns_record_ttl" in cf_data:
|
|
69
|
+
data["ttl"] = cf_data.get("ipaddress_dns_record_ttl")
|
|
70
|
+
|
|
71
|
+
if (disable_ptr := cf_data.get("ipaddress_dns_record_disable_ptr")) is not None:
|
|
72
|
+
data["disable_ptr"] = disable_ptr
|
|
73
|
+
|
|
74
|
+
return data
|
|
75
|
+
|
|
76
|
+
|
|
39
77
|
class RecordManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|
40
78
|
"""
|
|
41
79
|
Custom manager for records providing the activity status annotation
|
|
@@ -434,29 +472,70 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
434
472
|
self.rfc2317_cname_record.delete(save_zone_serial=save_zone_serial)
|
|
435
473
|
self.rfc2317_cname_record = None
|
|
436
474
|
|
|
437
|
-
def
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
name = dns_name.from_text(self.name, origin=None)
|
|
441
|
-
fqdn = dns_name.from_text(self.name, origin=_zone)
|
|
475
|
+
def update_from_ip_address(self, ip_address, zone=None):
|
|
476
|
+
if zone is None:
|
|
477
|
+
zone = self.zone
|
|
442
478
|
|
|
443
|
-
|
|
444
|
-
name.to_unicode()
|
|
479
|
+
data = record_data_from_ip_address(ip_address, zone)
|
|
445
480
|
|
|
446
|
-
|
|
447
|
-
self.
|
|
481
|
+
if data is None:
|
|
482
|
+
self.delete()
|
|
483
|
+
return
|
|
448
484
|
|
|
449
|
-
|
|
485
|
+
if all((getattr(self, attr) == data[attr] for attr in data.keys())):
|
|
486
|
+
return
|
|
487
|
+
|
|
488
|
+
for attr, value in data.items():
|
|
489
|
+
setattr(self, attr, value)
|
|
490
|
+
|
|
491
|
+
return self
|
|
492
|
+
|
|
493
|
+
@classmethod
|
|
494
|
+
def create_from_ip_address(cls, ip_address, zone):
|
|
495
|
+
data = record_data_from_ip_address(ip_address, zone)
|
|
496
|
+
|
|
497
|
+
if data is None:
|
|
498
|
+
return
|
|
499
|
+
|
|
500
|
+
return Record(
|
|
501
|
+
zone=zone,
|
|
502
|
+
managed=True,
|
|
503
|
+
ipam_ip_address=ip_address,
|
|
504
|
+
**data,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
def update_fqdn(self, zone=None):
|
|
508
|
+
if zone is None:
|
|
509
|
+
zone = self.zone
|
|
510
|
+
|
|
511
|
+
_zone = dns_name.from_text(zone.name, origin=dns_name.root)
|
|
512
|
+
name = dns_name.from_text(self.name, origin=None)
|
|
513
|
+
fqdn = dns_name.from_text(self.name, origin=_zone)
|
|
514
|
+
|
|
515
|
+
if not fqdn.is_subdomain(_zone):
|
|
450
516
|
raise ValidationError(
|
|
451
517
|
{
|
|
452
|
-
"name":
|
|
518
|
+
"name": f"{self.name} is not a name in {zone.name}",
|
|
453
519
|
}
|
|
454
520
|
)
|
|
455
521
|
|
|
456
|
-
|
|
522
|
+
_zone.to_unicode()
|
|
523
|
+
name.to_unicode()
|
|
524
|
+
|
|
525
|
+
self.name = name.relativize(_zone).to_text()
|
|
526
|
+
self.fqdn = fqdn.to_text()
|
|
527
|
+
|
|
528
|
+
def validate_name(self, new_zone=None):
|
|
529
|
+
if new_zone is None:
|
|
530
|
+
new_zone = self.zone
|
|
531
|
+
|
|
532
|
+
try:
|
|
533
|
+
self.update_fqdn(zone=new_zone)
|
|
534
|
+
|
|
535
|
+
except dns.exception.DNSException as exc:
|
|
457
536
|
raise ValidationError(
|
|
458
537
|
{
|
|
459
|
-
"name":
|
|
538
|
+
"name": str(exc),
|
|
460
539
|
}
|
|
461
540
|
)
|
|
462
541
|
|
|
@@ -488,15 +567,18 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
488
567
|
except ValidationError as exc:
|
|
489
568
|
raise ValidationError({"value": exc}) from None
|
|
490
569
|
|
|
491
|
-
def check_unique_record(self):
|
|
570
|
+
def check_unique_record(self, new_zone=None):
|
|
492
571
|
if not get_plugin_config("netbox_dns", "enforce_unique_records", False):
|
|
493
572
|
return
|
|
494
573
|
|
|
495
574
|
if not self.is_active:
|
|
496
575
|
return
|
|
497
576
|
|
|
577
|
+
if new_zone is None:
|
|
578
|
+
new_zone = self.zone
|
|
579
|
+
|
|
498
580
|
records = Record.objects.filter(
|
|
499
|
-
zone=
|
|
581
|
+
zone=new_zone,
|
|
500
582
|
name=self.name,
|
|
501
583
|
type=self.type,
|
|
502
584
|
value=self.value,
|
|
@@ -506,13 +588,41 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
506
588
|
if self.pk is not None:
|
|
507
589
|
records = records.exclude(pk=self.pk)
|
|
508
590
|
|
|
509
|
-
if
|
|
591
|
+
if records.exists():
|
|
592
|
+
if self.ipam_ip_address is not None:
|
|
593
|
+
if not records.filter(
|
|
594
|
+
ipam_ip_address__isnull=True
|
|
595
|
+
).exists() or get_plugin_config(
|
|
596
|
+
"netbox_dns", "dnssync_conflict_deactivate", False
|
|
597
|
+
):
|
|
598
|
+
return
|
|
599
|
+
|
|
510
600
|
raise ValidationError(
|
|
511
601
|
{
|
|
512
602
|
"value": f"There is already an active {self.type} record for name {self.name} in zone {self.zone} with value {self.value}."
|
|
513
603
|
}
|
|
514
604
|
) from None
|
|
515
605
|
|
|
606
|
+
def handle_conflicting_address_records(self):
|
|
607
|
+
if self.ipam_ip_address is None or not self.is_active:
|
|
608
|
+
return
|
|
609
|
+
|
|
610
|
+
if not get_plugin_config("netbox_dns", "dnssync_conflict_deactivate", False):
|
|
611
|
+
return
|
|
612
|
+
|
|
613
|
+
records = Record.objects.filter(
|
|
614
|
+
zone=self.zone,
|
|
615
|
+
name=self.name,
|
|
616
|
+
type=self.type,
|
|
617
|
+
value=self.value,
|
|
618
|
+
status__in=Record.ACTIVE_STATUS_LIST,
|
|
619
|
+
ipam_ip_address__isnull=True,
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
for record in records:
|
|
623
|
+
record.status = RecordStatusChoices.STATUS_INACTIVE
|
|
624
|
+
record.save(update_fields=["status"])
|
|
625
|
+
|
|
516
626
|
def check_unique_rrset_ttl(self):
|
|
517
627
|
if self.pk is not None:
|
|
518
628
|
return
|
|
@@ -520,6 +630,11 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
520
630
|
if not get_plugin_config("netbox_dns", "enforce_unique_rrset_ttl", False):
|
|
521
631
|
return
|
|
522
632
|
|
|
633
|
+
if self.ipam_ip_address is not None and get_plugin_config(
|
|
634
|
+
"netbox_dns", "dnssync_conflict_deactivate", False
|
|
635
|
+
):
|
|
636
|
+
return
|
|
637
|
+
|
|
523
638
|
if self.type == RecordTypeChoices.PTR and self.managed:
|
|
524
639
|
return
|
|
525
640
|
|
|
@@ -534,10 +649,13 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
534
649
|
.exclude(status=RecordStatusChoices.STATUS_INACTIVE)
|
|
535
650
|
)
|
|
536
651
|
|
|
652
|
+
if self.ipam_ip_address is not None:
|
|
653
|
+
records = records.exclude(ipam_ip_address__isnull=False)
|
|
654
|
+
|
|
537
655
|
if not records.exists():
|
|
538
656
|
return
|
|
539
657
|
|
|
540
|
-
conflicting_ttls = ", ".join(
|
|
658
|
+
conflicting_ttls = ", ".join({str(record.ttl) for record in records})
|
|
541
659
|
raise ValidationError(
|
|
542
660
|
{
|
|
543
661
|
"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})."
|
|
@@ -577,10 +695,10 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
577
695
|
self.type = self.type.upper()
|
|
578
696
|
super().clean_fields(*args, **kwargs)
|
|
579
697
|
|
|
580
|
-
def clean(self, *args, **kwargs):
|
|
581
|
-
self.validate_name()
|
|
698
|
+
def clean(self, *args, new_zone=None, **kwargs):
|
|
699
|
+
self.validate_name(new_zone=new_zone)
|
|
582
700
|
self.validate_value()
|
|
583
|
-
self.check_unique_record()
|
|
701
|
+
self.check_unique_record(new_zone=new_zone)
|
|
584
702
|
if self.pk is None:
|
|
585
703
|
self.check_unique_rrset_ttl()
|
|
586
704
|
|
|
@@ -686,6 +804,7 @@ class Record(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
686
804
|
self.ip_address = None
|
|
687
805
|
|
|
688
806
|
if self.is_address_record:
|
|
807
|
+
self.handle_conflicting_address_records()
|
|
689
808
|
self.update_ptr_record(
|
|
690
809
|
update_rfc2317_cname=update_rfc2317_cname,
|
|
691
810
|
save_zone_serial=save_zone_serial,
|
|
@@ -8,12 +8,12 @@ from taggit.managers import TaggableManager
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
__all__ = (
|
|
11
|
-
"
|
|
12
|
-
"
|
|
11
|
+
"RegistrationContact",
|
|
12
|
+
"RegistrationContactIndex",
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class
|
|
16
|
+
class RegistrationContact(NetBoxModel):
|
|
17
17
|
# +
|
|
18
18
|
# Data fields according to https://www.icann.org/resources/pages/rdds-labeling-policy-2017-02-01-en
|
|
19
19
|
# -
|
|
@@ -105,7 +105,7 @@ class Contact(NetBoxModel):
|
|
|
105
105
|
)
|
|
106
106
|
|
|
107
107
|
def get_absolute_url(self):
|
|
108
|
-
return reverse("plugins:netbox_dns:
|
|
108
|
+
return reverse("plugins:netbox_dns:registrationcontact", kwargs={"pk": self.pk})
|
|
109
109
|
|
|
110
110
|
def __str__(self):
|
|
111
111
|
if self.name is not None:
|
|
@@ -114,8 +114,8 @@ class Contact(NetBoxModel):
|
|
|
114
114
|
return self.contact_id
|
|
115
115
|
|
|
116
116
|
class Meta:
|
|
117
|
-
verbose_name = "Contact"
|
|
118
|
-
verbose_name_plural = "Contacts"
|
|
117
|
+
verbose_name = "Registration Contact"
|
|
118
|
+
verbose_name_plural = "Registration Contacts"
|
|
119
119
|
|
|
120
120
|
ordering = (
|
|
121
121
|
"name",
|
|
@@ -133,8 +133,8 @@ class Contact(NetBoxModel):
|
|
|
133
133
|
|
|
134
134
|
|
|
135
135
|
@register_search
|
|
136
|
-
class
|
|
137
|
-
model =
|
|
136
|
+
class RegistrationContactIndex(SearchIndex):
|
|
137
|
+
model = RegistrationContact
|
|
138
138
|
fields = (
|
|
139
139
|
("name", 100),
|
|
140
140
|
("contact_id", 100),
|
netbox_dns/models/view.py
CHANGED
|
@@ -29,6 +29,11 @@ class View(ObjectModificationMixin, ContactsMixin, NetBoxModel):
|
|
|
29
29
|
default_view = models.BooleanField(
|
|
30
30
|
default=False,
|
|
31
31
|
)
|
|
32
|
+
prefixes = models.ManyToManyField(
|
|
33
|
+
to="ipam.Prefix",
|
|
34
|
+
related_name="netbox_dns_views",
|
|
35
|
+
blank=True,
|
|
36
|
+
)
|
|
32
37
|
tenant = models.ForeignKey(
|
|
33
38
|
to="tenancy.Tenant",
|
|
34
39
|
on_delete=models.PROTECT,
|