netbox-plugin-dns 1.0.3__py3-none-any.whl → 1.0.5__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 +1 -1
- netbox_dns/api/nested_serializers.py +46 -1
- netbox_dns/api/serializers.py +2 -0
- netbox_dns/api/serializers_/contact.py +3 -0
- netbox_dns/api/serializers_/nameserver.py +4 -1
- netbox_dns/api/serializers_/record.py +5 -4
- netbox_dns/api/serializers_/record_template.py +57 -0
- netbox_dns/api/serializers_/registrar.py +3 -0
- netbox_dns/api/serializers_/view.py +3 -0
- netbox_dns/api/serializers_/zone.py +30 -6
- netbox_dns/api/serializers_/zone_template.py +129 -0
- netbox_dns/api/urls.py +4 -0
- netbox_dns/api/views.py +41 -1
- netbox_dns/choices/__init__.py +2 -0
- netbox_dns/choices/record.py +49 -0
- netbox_dns/choices/zone.py +20 -0
- netbox_dns/fields/address.py +6 -0
- netbox_dns/fields/network.py +3 -0
- netbox_dns/fields/rfc2317.py +3 -0
- netbox_dns/filtersets/__init__.py +3 -0
- netbox_dns/filtersets/contact.py +3 -0
- netbox_dns/filtersets/nameserver.py +3 -0
- netbox_dns/filtersets/record.py +5 -1
- netbox_dns/filtersets/record_template.py +54 -0
- netbox_dns/filtersets/registrar.py +3 -0
- netbox_dns/filtersets/view.py +3 -0
- netbox_dns/filtersets/zone.py +5 -8
- netbox_dns/filtersets/zone_template.py +116 -0
- netbox_dns/forms/__init__.py +2 -0
- netbox_dns/forms/contact.py +8 -0
- netbox_dns/forms/nameserver.py +8 -0
- netbox_dns/forms/record.py +25 -11
- netbox_dns/forms/record_template.py +220 -0
- netbox_dns/forms/registrar.py +8 -0
- netbox_dns/forms/view.py +10 -0
- netbox_dns/forms/zone.py +109 -36
- netbox_dns/forms/zone_template.py +298 -0
- netbox_dns/graphql/__init__.py +4 -0
- netbox_dns/graphql/filters.py +24 -1
- netbox_dns/graphql/schema.py +34 -1
- netbox_dns/graphql/types.py +73 -5
- netbox_dns/management/commands/cleanup_database.py +2 -6
- netbox_dns/management/commands/cleanup_rrset_ttl.py +2 -4
- netbox_dns/migrations/0001_squashed_netbox_dns_0_22.py +1 -2
- netbox_dns/migrations/0006_templating.py +172 -0
- netbox_dns/migrations/0021_record_ip_address.py +1 -1
- netbox_dns/mixins/object_modification.py +3 -0
- netbox_dns/models/__init__.py +7 -0
- netbox_dns/models/contact.py +6 -0
- netbox_dns/models/nameserver.py +8 -1
- netbox_dns/models/record.py +10 -122
- netbox_dns/models/record_template.py +180 -0
- netbox_dns/models/registrar.py +6 -0
- netbox_dns/models/view.py +7 -1
- netbox_dns/models/zone.py +57 -66
- netbox_dns/models/zone_template.py +149 -0
- netbox_dns/navigation.py +47 -0
- netbox_dns/tables/__init__.py +2 -0
- netbox_dns/tables/contact.py +3 -0
- netbox_dns/tables/nameserver.py +3 -2
- netbox_dns/tables/record.py +7 -3
- netbox_dns/tables/record_template.py +91 -0
- netbox_dns/tables/registrar.py +3 -0
- netbox_dns/tables/view.py +3 -0
- netbox_dns/tables/zone.py +3 -2
- netbox_dns/tables/zone_template.py +70 -0
- netbox_dns/template_content.py +2 -8
- netbox_dns/templates/netbox_dns/recordtemplate.html +84 -0
- netbox_dns/templates/netbox_dns/zonetemplate.html +86 -0
- netbox_dns/urls/__init__.py +4 -0
- netbox_dns/urls/record_template.py +65 -0
- netbox_dns/urls/zone_template.py +57 -0
- netbox_dns/utilities/ipam_coupling.py +2 -1
- netbox_dns/validators/__init__.py +1 -0
- netbox_dns/validators/dns_name.py +14 -9
- netbox_dns/validators/dns_value.py +83 -0
- netbox_dns/validators/rfc2317.py +7 -0
- netbox_dns/views/__init__.py +2 -0
- netbox_dns/views/contact.py +11 -0
- netbox_dns/views/nameserver.py +12 -0
- netbox_dns/views/record.py +14 -1
- netbox_dns/views/record_template.py +83 -0
- netbox_dns/views/registrar.py +12 -0
- netbox_dns/views/view.py +12 -0
- netbox_dns/views/zone.py +16 -0
- netbox_dns/views/zone_template.py +73 -0
- {netbox_plugin_dns-1.0.3.dist-info → netbox_plugin_dns-1.0.5.dist-info}/METADATA +2 -1
- netbox_plugin_dns-1.0.5.dist-info/RECORD +136 -0
- netbox_plugin_dns-1.0.3.dist-info/RECORD +0 -115
- {netbox_plugin_dns-1.0.3.dist-info → netbox_plugin_dns-1.0.5.dist-info}/LICENSE +0 -0
- {netbox_plugin_dns-1.0.3.dist-info → netbox_plugin_dns-1.0.5.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import dns
|
|
2
|
+
from dns import name as dns_name
|
|
3
|
+
|
|
4
|
+
from django.core.exceptions import ValidationError
|
|
5
|
+
from django.db import models
|
|
6
|
+
from django.urls import reverse
|
|
7
|
+
|
|
8
|
+
from netbox.models import NetBoxModel
|
|
9
|
+
from netbox.search import SearchIndex, register_search
|
|
10
|
+
from netbox.plugins.utils import get_plugin_config
|
|
11
|
+
|
|
12
|
+
from netbox_dns.choices import RecordTypeChoices, RecordStatusChoices
|
|
13
|
+
from netbox_dns.validators import validate_generic_name, validate_record_value
|
|
14
|
+
|
|
15
|
+
from .record import Record
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
__ALL__ = (
|
|
19
|
+
"RecordTemplate",
|
|
20
|
+
"RecordTemplateIndex",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class RecordTemplate(NetBoxModel):
|
|
25
|
+
name = models.CharField(
|
|
26
|
+
verbose_name="Template name",
|
|
27
|
+
unique=True,
|
|
28
|
+
max_length=200,
|
|
29
|
+
)
|
|
30
|
+
record_name = models.CharField(
|
|
31
|
+
verbose_name="Name",
|
|
32
|
+
max_length=255,
|
|
33
|
+
)
|
|
34
|
+
description = models.CharField(
|
|
35
|
+
max_length=200,
|
|
36
|
+
blank=True,
|
|
37
|
+
)
|
|
38
|
+
type = models.CharField(
|
|
39
|
+
choices=RecordTypeChoices,
|
|
40
|
+
)
|
|
41
|
+
value = models.CharField(
|
|
42
|
+
max_length=65535,
|
|
43
|
+
)
|
|
44
|
+
status = models.CharField(
|
|
45
|
+
choices=RecordStatusChoices,
|
|
46
|
+
default=RecordStatusChoices.STATUS_ACTIVE,
|
|
47
|
+
blank=False,
|
|
48
|
+
)
|
|
49
|
+
ttl = models.PositiveIntegerField(
|
|
50
|
+
verbose_name="TTL",
|
|
51
|
+
null=True,
|
|
52
|
+
blank=True,
|
|
53
|
+
)
|
|
54
|
+
disable_ptr = models.BooleanField(
|
|
55
|
+
verbose_name="Disable PTR",
|
|
56
|
+
help_text="Disable PTR record creation",
|
|
57
|
+
default=False,
|
|
58
|
+
)
|
|
59
|
+
tenant = models.ForeignKey(
|
|
60
|
+
to="tenancy.Tenant",
|
|
61
|
+
on_delete=models.PROTECT,
|
|
62
|
+
related_name="+",
|
|
63
|
+
blank=True,
|
|
64
|
+
null=True,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
clone_fields = (
|
|
68
|
+
"record_name",
|
|
69
|
+
"description",
|
|
70
|
+
"type",
|
|
71
|
+
"value",
|
|
72
|
+
"status",
|
|
73
|
+
"ttl",
|
|
74
|
+
"disable_ptr",
|
|
75
|
+
"tenant",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
template_fields = (
|
|
79
|
+
"type",
|
|
80
|
+
"value",
|
|
81
|
+
"status",
|
|
82
|
+
"ttl",
|
|
83
|
+
"disable_ptr",
|
|
84
|
+
"tenant",
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
class Meta:
|
|
88
|
+
ordering = ["name"]
|
|
89
|
+
|
|
90
|
+
def __str__(self):
|
|
91
|
+
return str(self.name)
|
|
92
|
+
|
|
93
|
+
def get_status_color(self):
|
|
94
|
+
return RecordStatusChoices.colors.get(self.status)
|
|
95
|
+
|
|
96
|
+
def get_absolute_url(self):
|
|
97
|
+
return reverse("plugins:netbox_dns:recordtemplate", kwargs={"pk": self.pk})
|
|
98
|
+
|
|
99
|
+
def validate_name(self):
|
|
100
|
+
try:
|
|
101
|
+
name = dns_name.from_text(self.record_name, origin=None)
|
|
102
|
+
name.to_unicode()
|
|
103
|
+
|
|
104
|
+
except dns.exception.DNSException as exc:
|
|
105
|
+
raise ValidationError({"record_name": str(exc)})
|
|
106
|
+
|
|
107
|
+
if self.type not in get_plugin_config(
|
|
108
|
+
"netbox_dns", "tolerate_non_rfc1035_types", default=[]
|
|
109
|
+
):
|
|
110
|
+
try:
|
|
111
|
+
validate_generic_name(
|
|
112
|
+
self.record_name,
|
|
113
|
+
(
|
|
114
|
+
self.type
|
|
115
|
+
in get_plugin_config(
|
|
116
|
+
"netbox_dns",
|
|
117
|
+
"tolerate_leading_underscore_types",
|
|
118
|
+
default=[],
|
|
119
|
+
)
|
|
120
|
+
),
|
|
121
|
+
)
|
|
122
|
+
except ValidationError as exc:
|
|
123
|
+
raise ValidationError(
|
|
124
|
+
{
|
|
125
|
+
"record_name": exc,
|
|
126
|
+
}
|
|
127
|
+
) from None
|
|
128
|
+
|
|
129
|
+
def validate_value(self):
|
|
130
|
+
try:
|
|
131
|
+
validate_record_value(self.type, self.value)
|
|
132
|
+
except ValidationError as exc:
|
|
133
|
+
raise ValidationError({"value": exc}) from None
|
|
134
|
+
|
|
135
|
+
def matching_records(self, zone):
|
|
136
|
+
return Record.objects.filter(
|
|
137
|
+
zone=zone, name=self.record_name, type=self.type, value=self.value
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def create_record(self, zone):
|
|
141
|
+
if self.matching_records(zone).exists():
|
|
142
|
+
return
|
|
143
|
+
|
|
144
|
+
record_data = {
|
|
145
|
+
"zone": zone,
|
|
146
|
+
"name": self.record_name,
|
|
147
|
+
}
|
|
148
|
+
for field in self.template_fields:
|
|
149
|
+
record_data[field] = getattr(self, field)
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
record = Record.objects.create(**record_data)
|
|
153
|
+
except ValidationError as exc:
|
|
154
|
+
raise ValidationError(
|
|
155
|
+
f"Error while processing record template {self}: {exc.messages[0]}"
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if tags := self.tags.all():
|
|
159
|
+
record.tags.set(tags)
|
|
160
|
+
|
|
161
|
+
def clean_fields(self, *args, **kwargs):
|
|
162
|
+
self.type = self.type.upper()
|
|
163
|
+
super().clean_fields(*args, **kwargs)
|
|
164
|
+
|
|
165
|
+
def clean(self, *args, **kwargs):
|
|
166
|
+
self.validate_name()
|
|
167
|
+
self.validate_value()
|
|
168
|
+
|
|
169
|
+
super().clean(*args, **kwargs)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@register_search
|
|
173
|
+
class RecordTemplateIndex(SearchIndex):
|
|
174
|
+
model = RecordTemplate
|
|
175
|
+
fields = (
|
|
176
|
+
("name", 100),
|
|
177
|
+
("record_name", 120),
|
|
178
|
+
("value", 150),
|
|
179
|
+
("type", 200),
|
|
180
|
+
)
|
netbox_dns/models/registrar.py
CHANGED
|
@@ -5,6 +5,12 @@ from netbox.models import NetBoxModel
|
|
|
5
5
|
from netbox.search import SearchIndex, register_search
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
__ALL__ = (
|
|
9
|
+
"Registrar",
|
|
10
|
+
"RegistrarIndex",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
8
14
|
class Registrar(NetBoxModel):
|
|
9
15
|
# +
|
|
10
16
|
# Data fields according to https://www.icann.org/resources/pages/rdds-labeling-policy-2017-02-01-en
|
netbox_dns/models/view.py
CHANGED
|
@@ -10,6 +10,12 @@ from utilities.exceptions import AbortRequest
|
|
|
10
10
|
from netbox_dns.mixins import ObjectModificationMixin
|
|
11
11
|
|
|
12
12
|
|
|
13
|
+
__ALL__ = (
|
|
14
|
+
"View",
|
|
15
|
+
"ViewIndex",
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
13
19
|
class View(ObjectModificationMixin, NetBoxModel):
|
|
14
20
|
name = models.CharField(
|
|
15
21
|
unique=True,
|
|
@@ -54,7 +60,7 @@ class View(ObjectModificationMixin, NetBoxModel):
|
|
|
54
60
|
|
|
55
61
|
super().delete(*args, **kwargs)
|
|
56
62
|
|
|
57
|
-
def clean(self, *args,
|
|
63
|
+
def clean(self, *args, **kwargs):
|
|
58
64
|
if (changed_fields := self.changed_fields) is None:
|
|
59
65
|
return
|
|
60
66
|
|
netbox_dns/models/zone.py
CHANGED
|
@@ -22,9 +22,9 @@ from netbox.models import NetBoxModel
|
|
|
22
22
|
from netbox.search import SearchIndex, register_search
|
|
23
23
|
from netbox.plugins.utils import get_plugin_config
|
|
24
24
|
from utilities.querysets import RestrictedQuerySet
|
|
25
|
-
from utilities.choices import ChoiceSet
|
|
26
25
|
from ipam.models import IPAddress
|
|
27
26
|
|
|
27
|
+
from netbox_dns.choices import RecordClassChoices, RecordTypeChoices, ZoneStatusChoices
|
|
28
28
|
from netbox_dns.fields import NetworkField, RFC2317NetworkField
|
|
29
29
|
from netbox_dns.utilities import (
|
|
30
30
|
arpa_to_prefix,
|
|
@@ -38,12 +38,15 @@ from netbox_dns.validators import (
|
|
|
38
38
|
)
|
|
39
39
|
from netbox_dns.mixins import ObjectModificationMixin
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
from .record import Record
|
|
42
|
+
from .view import View
|
|
43
|
+
from .nameserver import NameServer
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__ALL__ = (
|
|
47
|
+
"Zone",
|
|
48
|
+
"ZoneIndex",
|
|
49
|
+
)
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
class ZoneManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|
@@ -63,24 +66,15 @@ class ZoneManager(models.Manager.from_queryset(RestrictedQuerySet)):
|
|
|
63
66
|
)
|
|
64
67
|
|
|
65
68
|
|
|
66
|
-
class
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
STATUS_ACTIVE = "active"
|
|
70
|
-
STATUS_RESERVED = "reserved"
|
|
71
|
-
STATUS_DEPRECATED = "deprecated"
|
|
72
|
-
STATUS_PARKED = "parked"
|
|
69
|
+
class Zone(ObjectModificationMixin, NetBoxModel):
|
|
70
|
+
ACTIVE_STATUS_LIST = (ZoneStatusChoices.STATUS_ACTIVE,)
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
(
|
|
76
|
-
(STATUS_RESERVED, "Reserved", "cyan"),
|
|
77
|
-
(STATUS_DEPRECATED, "Deprecated", "red"),
|
|
78
|
-
(STATUS_PARKED, "Parked", "gray"),
|
|
79
|
-
]
|
|
72
|
+
def __init__(self, *args, **kwargs):
|
|
73
|
+
kwargs.pop("template", None)
|
|
80
74
|
|
|
75
|
+
super().__init__(*args, **kwargs)
|
|
81
76
|
|
|
82
|
-
|
|
83
|
-
ACTIVE_STATUS_LIST = (ZoneStatusChoices.STATUS_ACTIVE,)
|
|
77
|
+
self._soa_serial_dirty = False
|
|
84
78
|
|
|
85
79
|
view = models.ForeignKey(
|
|
86
80
|
to="View",
|
|
@@ -249,8 +243,6 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
249
243
|
null=True,
|
|
250
244
|
)
|
|
251
245
|
|
|
252
|
-
soa_serial_dirty = False
|
|
253
|
-
|
|
254
246
|
objects = ZoneManager()
|
|
255
247
|
|
|
256
248
|
clone_fields = [
|
|
@@ -288,8 +280,11 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
288
280
|
except dns_name.IDNAException:
|
|
289
281
|
name = self.name
|
|
290
282
|
|
|
291
|
-
|
|
292
|
-
|
|
283
|
+
try:
|
|
284
|
+
if not self.view.default_view:
|
|
285
|
+
return f"[{self.view}] {name}"
|
|
286
|
+
except ObjectDoesNotExist:
|
|
287
|
+
return f"[<no view assigned>] {name}"
|
|
293
288
|
|
|
294
289
|
return str(name)
|
|
295
290
|
|
|
@@ -302,6 +297,14 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
302
297
|
and field not in ("zone_soa_mname", "zone_nameservers")
|
|
303
298
|
}
|
|
304
299
|
|
|
300
|
+
@property
|
|
301
|
+
def soa_serial_dirty(self):
|
|
302
|
+
return self._soa_serial_dirty
|
|
303
|
+
|
|
304
|
+
@soa_serial_dirty.setter
|
|
305
|
+
def soa_serial_dirty(self, soa_serial_dirty):
|
|
306
|
+
self._soa_serial_dirty = soa_serial_dirty
|
|
307
|
+
|
|
305
308
|
@property
|
|
306
309
|
def display_name(self):
|
|
307
310
|
return name_to_unicode(self.name)
|
|
@@ -371,7 +374,7 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
371
374
|
return None
|
|
372
375
|
|
|
373
376
|
def record_count(self, managed=False):
|
|
374
|
-
return
|
|
377
|
+
return Record.objects.filter(zone=self, managed=managed).count()
|
|
375
378
|
|
|
376
379
|
def rfc2317_child_zone_count(self):
|
|
377
380
|
return Zone.objects.filter(rfc2317_parent_zone=self).count()
|
|
@@ -380,8 +383,8 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
380
383
|
soa_name = "@"
|
|
381
384
|
soa_ttl = self.soa_ttl
|
|
382
385
|
soa_rdata = SOA.SOA(
|
|
383
|
-
rdclass=
|
|
384
|
-
rdtype=
|
|
386
|
+
rdclass=RecordClassChoices.IN,
|
|
387
|
+
rdtype=RecordTypeChoices.SOA,
|
|
385
388
|
mname=self.soa_mname.name,
|
|
386
389
|
rname=self.soa_rname,
|
|
387
390
|
serial=self.soa_serial,
|
|
@@ -392,9 +395,7 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
392
395
|
)
|
|
393
396
|
|
|
394
397
|
try:
|
|
395
|
-
soa_record = self.record_set.get(
|
|
396
|
-
type=record.RecordTypeChoices.SOA, name=soa_name
|
|
397
|
-
)
|
|
398
|
+
soa_record = self.record_set.get(type=RecordTypeChoices.SOA, name=soa_name)
|
|
398
399
|
|
|
399
400
|
if soa_record.ttl != soa_ttl or soa_record.value != soa_rdata.to_text():
|
|
400
401
|
soa_record.ttl = soa_ttl
|
|
@@ -402,10 +403,10 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
402
403
|
soa_record.managed = True
|
|
403
404
|
soa_record.save()
|
|
404
405
|
|
|
405
|
-
except
|
|
406
|
-
|
|
406
|
+
except Record.DoesNotExist:
|
|
407
|
+
Record.objects.create(
|
|
407
408
|
zone_id=self.pk,
|
|
408
|
-
type=
|
|
409
|
+
type=RecordTypeChoices.SOA,
|
|
409
410
|
name=soa_name,
|
|
410
411
|
ttl=soa_ttl,
|
|
411
412
|
value=soa_rdata.to_text(),
|
|
@@ -417,14 +418,14 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
417
418
|
|
|
418
419
|
nameservers = [f"{nameserver.name}." for nameserver in self.nameservers.all()]
|
|
419
420
|
|
|
420
|
-
self.record_set.filter(type=
|
|
421
|
+
self.record_set.filter(type=RecordTypeChoices.NS, managed=True).exclude(
|
|
421
422
|
value__in=nameservers
|
|
422
423
|
).delete()
|
|
423
424
|
|
|
424
425
|
for ns in nameservers:
|
|
425
|
-
|
|
426
|
+
Record.raw_objects.update_or_create(
|
|
426
427
|
zone_id=self.pk,
|
|
427
|
-
type=
|
|
428
|
+
type=RecordTypeChoices.NS,
|
|
428
429
|
name=ns_name,
|
|
429
430
|
value=ns,
|
|
430
431
|
managed=True,
|
|
@@ -452,14 +453,11 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
452
453
|
continue
|
|
453
454
|
|
|
454
455
|
relative_name = name.relativize(parent).to_text()
|
|
455
|
-
address_records =
|
|
456
|
+
address_records = Record.objects.filter(
|
|
456
457
|
Q(zone=ns_zone),
|
|
457
|
-
Q(status__in=
|
|
458
|
+
Q(status__in=Record.ACTIVE_STATUS_LIST),
|
|
458
459
|
Q(Q(name=f"{_nameserver.name}.") | Q(name=relative_name)),
|
|
459
|
-
Q(
|
|
460
|
-
Q(type=record.RecordTypeChoices.A)
|
|
461
|
-
| Q(type=record.RecordTypeChoices.AAAA)
|
|
462
|
-
),
|
|
460
|
+
Q(Q(type=RecordTypeChoices.A) | Q(type=RecordTypeChoices.AAAA)),
|
|
463
461
|
)
|
|
464
462
|
|
|
465
463
|
if not address_records:
|
|
@@ -482,8 +480,8 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
482
480
|
)
|
|
483
481
|
|
|
484
482
|
def get_auto_serial(self):
|
|
485
|
-
records =
|
|
486
|
-
type=
|
|
483
|
+
records = Record.objects.filter(zone_id=self.pk).exclude(
|
|
484
|
+
type=RecordTypeChoices.SOA
|
|
487
485
|
)
|
|
488
486
|
if records:
|
|
489
487
|
soa_serial = (
|
|
@@ -541,7 +539,7 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
541
539
|
self.save(update_fields=["rfc2317_parent_zone"])
|
|
542
540
|
|
|
543
541
|
ptr_records = self.record_set.filter(
|
|
544
|
-
type=
|
|
542
|
+
type=RecordTypeChoices.PTR
|
|
545
543
|
).prefetch_related("zone", "rfc2317_cname_record")
|
|
546
544
|
ptr_zones = {ptr_record.zone for ptr_record in ptr_records}
|
|
547
545
|
|
|
@@ -576,7 +574,7 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
576
574
|
defaults = settings.PLUGINS_CONFIG.get("netbox_dns")
|
|
577
575
|
|
|
578
576
|
if self.view_id is None:
|
|
579
|
-
self.view_id =
|
|
577
|
+
self.view_id = View.get_default_view().pk
|
|
580
578
|
|
|
581
579
|
for field, value in self.get_defaults().items():
|
|
582
580
|
if getattr(self, field) in (None, ""):
|
|
@@ -586,10 +584,8 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
586
584
|
if self.soa_mname_id is None:
|
|
587
585
|
default_soa_mname = defaults.get("zone_soa_mname")
|
|
588
586
|
try:
|
|
589
|
-
self.soa_mname =
|
|
590
|
-
|
|
591
|
-
)
|
|
592
|
-
except nameserver.NameServer.DoesNotExist:
|
|
587
|
+
self.soa_mname = NameServer.objects.get(name=default_soa_mname)
|
|
588
|
+
except NameServer.DoesNotExist:
|
|
593
589
|
raise ValidationError(
|
|
594
590
|
f"Default soa_mname instance {default_soa_mname} does not exist"
|
|
595
591
|
)
|
|
@@ -715,12 +711,9 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
715
711
|
view=self.view,
|
|
716
712
|
arpa_network__net_contains_or_equals=self.arpa_network,
|
|
717
713
|
)
|
|
718
|
-
address_records =
|
|
714
|
+
address_records = Record.objects.filter(
|
|
719
715
|
Q(ptr_record__isnull=True) | Q(ptr_record__zone__in=zones),
|
|
720
|
-
type__in=(
|
|
721
|
-
record.RecordTypeChoices.A,
|
|
722
|
-
record.RecordTypeChoices.AAAA,
|
|
723
|
-
),
|
|
716
|
+
type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA),
|
|
724
717
|
disable_ptr=False,
|
|
725
718
|
)
|
|
726
719
|
|
|
@@ -749,11 +742,11 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
749
742
|
view=self.view,
|
|
750
743
|
arpa_network__net_contains=self.rfc2317_prefix,
|
|
751
744
|
)
|
|
752
|
-
address_records =
|
|
745
|
+
address_records = Record.objects.filter(
|
|
753
746
|
Q(ptr_record__isnull=True)
|
|
754
747
|
| Q(ptr_record__zone__in=zones)
|
|
755
748
|
| Q(ptr_record__zone=self),
|
|
756
|
-
type=
|
|
749
|
+
type=RecordTypeChoices.A,
|
|
757
750
|
disable_ptr=False,
|
|
758
751
|
)
|
|
759
752
|
|
|
@@ -771,7 +764,7 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
771
764
|
|
|
772
765
|
elif changed_fields is not None and {"name", "view", "status"} & changed_fields:
|
|
773
766
|
for address_record in self.record_set.filter(
|
|
774
|
-
type__in=(
|
|
767
|
+
type__in=(RecordTypeChoices.A, RecordTypeChoices.AAAA)
|
|
775
768
|
):
|
|
776
769
|
address_record.save(update_fields=["ptr_record"])
|
|
777
770
|
|
|
@@ -809,9 +802,7 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
809
802
|
ptr_records = self.record_set.filter(address_record__isnull=False)
|
|
810
803
|
update_records = [
|
|
811
804
|
address_record.pk
|
|
812
|
-
for address_record in
|
|
813
|
-
ptr_record__in=ptr_records
|
|
814
|
-
)
|
|
805
|
+
for address_record in Record.objects.filter(ptr_record__in=ptr_records)
|
|
815
806
|
]
|
|
816
807
|
|
|
817
808
|
cname_records = {
|
|
@@ -842,9 +833,9 @@ class Zone(ObjectModificationMixin, NetBoxModel):
|
|
|
842
833
|
|
|
843
834
|
super().delete(*args, **kwargs)
|
|
844
835
|
|
|
845
|
-
address_records =
|
|
846
|
-
|
|
847
|
-
)
|
|
836
|
+
address_records = Record.objects.filter(pk__in=update_records).prefetch_related(
|
|
837
|
+
"zone"
|
|
838
|
+
)
|
|
848
839
|
|
|
849
840
|
for address_record in address_records:
|
|
850
841
|
address_record.save(save_zone_serial=False)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
from django.db import models
|
|
2
|
+
from django.urls import reverse
|
|
3
|
+
|
|
4
|
+
from netbox.models import NetBoxModel
|
|
5
|
+
from netbox.search import SearchIndex, register_search
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
__ALL__ = (
|
|
9
|
+
"ZoneTemplate",
|
|
10
|
+
"ZoneTemplateIndex",
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ZoneTemplate(NetBoxModel):
|
|
15
|
+
name = models.CharField(
|
|
16
|
+
verbose_name="Template name",
|
|
17
|
+
unique=True,
|
|
18
|
+
max_length=200,
|
|
19
|
+
)
|
|
20
|
+
description = models.CharField(
|
|
21
|
+
max_length=200,
|
|
22
|
+
blank=True,
|
|
23
|
+
)
|
|
24
|
+
nameservers = models.ManyToManyField(
|
|
25
|
+
to="NameServer",
|
|
26
|
+
related_name="+",
|
|
27
|
+
blank=True,
|
|
28
|
+
)
|
|
29
|
+
record_templates = models.ManyToManyField(
|
|
30
|
+
to="RecordTemplate",
|
|
31
|
+
related_name="zone_templates",
|
|
32
|
+
blank=True,
|
|
33
|
+
)
|
|
34
|
+
tenant = models.ForeignKey(
|
|
35
|
+
to="tenancy.Tenant",
|
|
36
|
+
on_delete=models.SET_NULL,
|
|
37
|
+
related_name="+",
|
|
38
|
+
blank=True,
|
|
39
|
+
null=True,
|
|
40
|
+
)
|
|
41
|
+
registrar = models.ForeignKey(
|
|
42
|
+
to="Registrar",
|
|
43
|
+
on_delete=models.SET_NULL,
|
|
44
|
+
related_name="+",
|
|
45
|
+
help_text="The external registrar the domain is registered with",
|
|
46
|
+
blank=True,
|
|
47
|
+
null=True,
|
|
48
|
+
)
|
|
49
|
+
registrant = models.ForeignKey(
|
|
50
|
+
to="Contact",
|
|
51
|
+
on_delete=models.SET_NULL,
|
|
52
|
+
related_name="+",
|
|
53
|
+
help_text="The owner of the domain",
|
|
54
|
+
blank=True,
|
|
55
|
+
null=True,
|
|
56
|
+
)
|
|
57
|
+
admin_c = models.ForeignKey(
|
|
58
|
+
to="Contact",
|
|
59
|
+
on_delete=models.SET_NULL,
|
|
60
|
+
verbose_name="Admin contact",
|
|
61
|
+
related_name="+",
|
|
62
|
+
help_text="The administrative contact for the domain",
|
|
63
|
+
blank=True,
|
|
64
|
+
null=True,
|
|
65
|
+
)
|
|
66
|
+
tech_c = models.ForeignKey(
|
|
67
|
+
to="Contact",
|
|
68
|
+
on_delete=models.SET_NULL,
|
|
69
|
+
verbose_name="Tech contact",
|
|
70
|
+
related_name="+",
|
|
71
|
+
help_text="The technical contact for the domain",
|
|
72
|
+
blank=True,
|
|
73
|
+
null=True,
|
|
74
|
+
)
|
|
75
|
+
billing_c = models.ForeignKey(
|
|
76
|
+
to="Contact",
|
|
77
|
+
on_delete=models.SET_NULL,
|
|
78
|
+
verbose_name="Billing contact",
|
|
79
|
+
related_name="+",
|
|
80
|
+
help_text="The billing contact for the domain",
|
|
81
|
+
blank=True,
|
|
82
|
+
null=True,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
clone_fields = [
|
|
86
|
+
"description",
|
|
87
|
+
"nameservers",
|
|
88
|
+
"tenant",
|
|
89
|
+
"registrar",
|
|
90
|
+
"registrant",
|
|
91
|
+
"admin_c",
|
|
92
|
+
"tech_c",
|
|
93
|
+
"billing_c",
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
template_fields = [
|
|
97
|
+
"tenant",
|
|
98
|
+
"registrar",
|
|
99
|
+
"registrant",
|
|
100
|
+
"admin_c",
|
|
101
|
+
"tech_c",
|
|
102
|
+
"billing_c",
|
|
103
|
+
]
|
|
104
|
+
|
|
105
|
+
class Meta:
|
|
106
|
+
ordering = ["name"]
|
|
107
|
+
|
|
108
|
+
def __str__(self):
|
|
109
|
+
return str(self.name)
|
|
110
|
+
|
|
111
|
+
def get_absolute_url(self):
|
|
112
|
+
return reverse("plugins:netbox_dns:zonetemplate", kwargs={"pk": self.pk})
|
|
113
|
+
|
|
114
|
+
def apply_to_zone(self, zone):
|
|
115
|
+
if not zone.nameservers.all() and self.nameservers.all():
|
|
116
|
+
zone.nameservers.set(self.nameservers.all())
|
|
117
|
+
|
|
118
|
+
if not zone.tags.all() and self.tags.all():
|
|
119
|
+
zone.tags.set(self.tags.all())
|
|
120
|
+
|
|
121
|
+
fields_changed = True
|
|
122
|
+
for field in self.template_fields:
|
|
123
|
+
if getattr(zone, field) is None and getattr(self, field) is not None:
|
|
124
|
+
fields_changed = True
|
|
125
|
+
setattr(zone, field, getattr(self, field))
|
|
126
|
+
|
|
127
|
+
if fields_changed:
|
|
128
|
+
zone.save()
|
|
129
|
+
|
|
130
|
+
self.create_records(zone)
|
|
131
|
+
|
|
132
|
+
def create_records(self, zone):
|
|
133
|
+
for record_template in self.record_templates.all():
|
|
134
|
+
record_template.create_record(zone=zone)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@register_search
|
|
138
|
+
class ZoneTemplateIndex(SearchIndex):
|
|
139
|
+
model = ZoneTemplate
|
|
140
|
+
fields = (
|
|
141
|
+
("name", 100),
|
|
142
|
+
("tenant", 300),
|
|
143
|
+
("registrar", 300),
|
|
144
|
+
("registrant", 300),
|
|
145
|
+
("admin_c", 300),
|
|
146
|
+
("tech_c", 300),
|
|
147
|
+
("billing_c", 300),
|
|
148
|
+
("description", 500),
|
|
149
|
+
)
|