netbox-plugin-dns 1.0.1__tar.gz → 1.0.3__tar.gz

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.

Files changed (116) hide show
  1. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/LICENSE +2 -1
  2. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/PKG-INFO +2 -1
  3. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/README.md +1 -0
  4. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/__init__.py +6 -2
  5. netbox_plugin_dns-1.0.3/netbox_dns/mixins/__init__.py +1 -0
  6. netbox_plugin_dns-1.0.3/netbox_dns/mixins/object_modification.py +26 -0
  7. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/models/nameserver.py +4 -5
  8. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/models/record.py +20 -9
  9. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/models/view.py +10 -8
  10. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/models/zone.py +28 -22
  11. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/navigation.py +34 -18
  12. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/tables/view.py +1 -1
  13. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/record.html +4 -4
  14. netbox_plugin_dns-1.0.3/netbox_dns/templates/netbox_dns/zone/child_zone.html +18 -0
  15. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/zone.html +11 -15
  16. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/urls/zone.py +6 -0
  17. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/utilities/__init__.py +1 -1
  18. netbox_plugin_dns-1.0.3/netbox_dns/validators/dns_name.py +86 -0
  19. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/views/zone.py +20 -0
  20. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/pyproject.toml +1 -1
  21. netbox_plugin_dns-1.0.1/netbox_dns/validators/dns_name.py +0 -65
  22. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/api/nested_serializers.py +0 -0
  23. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/api/serializers.py +0 -0
  24. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/api/serializers_/__init__.py +0 -0
  25. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/api/serializers_/contact.py +0 -0
  26. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/api/serializers_/nameserver.py +0 -0
  27. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/api/serializers_/record.py +0 -0
  28. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/api/serializers_/registrar.py +0 -0
  29. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/api/serializers_/view.py +0 -0
  30. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/api/serializers_/zone.py +0 -0
  31. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/api/urls.py +0 -0
  32. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/api/views.py +0 -0
  33. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/apps.py +0 -0
  34. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/fields/__init__.py +0 -0
  35. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/fields/address.py +0 -0
  36. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/fields/network.py +0 -0
  37. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/fields/rfc2317.py +0 -0
  38. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/filtersets/__init__.py +0 -0
  39. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/filtersets/contact.py +0 -0
  40. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/filtersets/nameserver.py +0 -0
  41. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/filtersets/record.py +0 -0
  42. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/filtersets/registrar.py +0 -0
  43. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/filtersets/view.py +0 -0
  44. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/filtersets/zone.py +0 -0
  45. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/forms/__init__.py +0 -0
  46. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/forms/contact.py +0 -0
  47. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/forms/nameserver.py +0 -0
  48. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/forms/record.py +0 -0
  49. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/forms/registrar.py +0 -0
  50. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/forms/view.py +0 -0
  51. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/forms/zone.py +0 -0
  52. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/graphql/__init__.py +0 -0
  53. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/graphql/filters.py +0 -0
  54. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/graphql/schema.py +0 -0
  55. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/graphql/types.py +0 -0
  56. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/management/commands/cleanup_database.py +0 -0
  57. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/management/commands/cleanup_rrset_ttl.py +0 -0
  58. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/management/commands/setup_coupling.py +0 -0
  59. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/management/commands/update_soa.py +0 -0
  60. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0001_squashed_netbox_dns_0_15.py +0 -0
  61. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0001_squashed_netbox_dns_0_22.py +0 -0
  62. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0002_contact_description_registrar_description.py +0 -0
  63. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0003_default_view.py +0 -0
  64. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0004_create_and_assign_default_view.py +0 -0
  65. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0005_alter_zone_view_not_null.py +0 -0
  66. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0020_netbox_3_4.py +0 -0
  67. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0021_record_ip_address.py +0 -0
  68. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0022_search.py +0 -0
  69. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0023_alter_record_value.py +0 -0
  70. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0024_tenancy.py +0 -0
  71. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0025_ipam_coupling_cf.py +0 -0
  72. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0026_domain_registration.py +0 -0
  73. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0027_alter_registrar_iana_id.py +0 -0
  74. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0028_rfc2317_fields.py +0 -0
  75. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/0029_record_fqdn.py +0 -0
  76. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/migrations/__init__.py +0 -0
  77. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/models/__init__.py +0 -0
  78. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/models/contact.py +0 -0
  79. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/models/registrar.py +0 -0
  80. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/signals/__init__.py +0 -0
  81. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/signals/ipam_coupling.py +0 -0
  82. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/tables/__init__.py +0 -0
  83. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/tables/contact.py +0 -0
  84. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/tables/nameserver.py +0 -0
  85. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/tables/record.py +0 -0
  86. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/tables/registrar.py +0 -0
  87. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/tables/zone.py +0 -0
  88. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/template_content.py +0 -0
  89. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/contact.html +0 -0
  90. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/nameserver.html +0 -0
  91. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/record/managed.html +0 -0
  92. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/record/related.html +0 -0
  93. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/registrar.html +0 -0
  94. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/related_dns_objects.html +0 -0
  95. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/view.html +0 -0
  96. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/zone/base.html +0 -0
  97. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/zone/child.html +0 -0
  98. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/zone/managed_record.html +0 -0
  99. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/zone/record.html +0 -0
  100. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/zone/registration.html +0 -0
  101. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/templates/netbox_dns/zone/rfc2317_child_zone.html +0 -0
  102. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/urls/__init__.py +0 -0
  103. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/urls/contact.py +0 -0
  104. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/urls/nameserver.py +0 -0
  105. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/urls/record.py +0 -0
  106. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/urls/registrar.py +0 -0
  107. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/urls/view.py +0 -0
  108. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/utilities/ipam_coupling.py +0 -0
  109. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/validators/__init__.py +0 -0
  110. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/validators/rfc2317.py +0 -0
  111. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/views/__init__.py +0 -0
  112. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/views/contact.py +0 -0
  113. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/views/nameserver.py +0 -0
  114. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/views/record.py +0 -0
  115. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/views/registrar.py +0 -0
  116. {netbox_plugin_dns-1.0.1 → netbox_plugin_dns-1.0.3}/netbox_dns/views/view.py +0 -0
@@ -1,6 +1,7 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2021 Aurora Research Lab
3
+ Copyright (c) 2021-2023 Aurora Research Lab
4
+ Copyright (c) 2023 Peter Eckel
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: netbox-plugin-dns
3
- Version: 1.0.1
3
+ Version: 1.0.3
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
@@ -44,6 +44,7 @@ The main focus of the plugin is to ensure the quality of the data stored in it.
44
44
  * Validation of record names and values
45
45
  * Automatic maintenance of PTR records for IPv6 and IPv4 address records
46
46
  * Automatic generation of SOA records, optionally including the serial number of the zone data
47
+ * Validation of changes to the SOA SERIAL number, whether they are done automatically or manually
47
48
  * Validation of record types such as CNAME and singletons, to ensure DNS zone validity
48
49
  * Support for [RFC 2317](https://datatracker.ietf.org/doc/html/rfc2317) delegation of PTR zones for IPv4 subnets longer than 24 bits
49
50
 
@@ -24,6 +24,7 @@ The main focus of the plugin is to ensure the quality of the data stored in it.
24
24
  * Validation of record names and values
25
25
  * Automatic maintenance of PTR records for IPv6 and IPv4 address records
26
26
  * Automatic generation of SOA records, optionally including the serial number of the zone data
27
+ * Validation of changes to the SOA SERIAL number, whether they are done automatically or manually
27
28
  * Validation of record types such as CNAME and singletons, to ensure DNS zone validity
28
29
  * Support for [RFC 2317](https://datatracker.ietf.org/doc/html/rfc2317) delegation of PTR zones for IPv4 subnets longer than 24 bits
29
30
 
@@ -1,6 +1,6 @@
1
1
  from netbox.plugins import PluginConfig
2
2
 
3
- __version__ = "1.0.1"
3
+ __version__ = "1.0.3"
4
4
 
5
5
 
6
6
  class DNSConfig(PluginConfig):
@@ -21,7 +21,9 @@ class DNSConfig(PluginConfig):
21
21
  "zone_soa_expire": 2419200,
22
22
  "zone_soa_minimum": 3600,
23
23
  "feature_ipam_coupling": False,
24
- "tolerate_underscores_in_hostnames": False,
24
+ "tolerate_characters_in_zone_labels": "",
25
+ "tolerate_underscores_in_labels": False,
26
+ "tolerate_underscores_in_hostnames": False, # Deprecated, will be removed in 1.2.0
25
27
  "tolerate_leading_underscore_types": [
26
28
  "TXT",
27
29
  "SRV",
@@ -30,6 +32,8 @@ class DNSConfig(PluginConfig):
30
32
  "enable_root_zones": False,
31
33
  "enforce_unique_records": True,
32
34
  "enforce_unique_rrset_ttl": True,
35
+ "menu_name": "NetBox DNS",
36
+ "top_level_menu": True,
33
37
  }
34
38
  base_url = "netbox-dns"
35
39
 
@@ -0,0 +1 @@
1
+ from .object_modification import *
@@ -0,0 +1,26 @@
1
+ from netbox.models import NetBoxModel
2
+
3
+
4
+ class ObjectModificationMixin:
5
+ def __init__(self, *args, **kwargs):
6
+ super().__init__(*args, **kwargs)
7
+
8
+ if not hasattr(self.__class__, "check_fields"):
9
+ self.__class__.check_fields = (
10
+ {field.name for field in self._meta.fields}
11
+ - {field.name for field in NetBoxModel._meta.fields}
12
+ - {"id"}
13
+ )
14
+
15
+ @property
16
+ def changed_fields(self):
17
+ if self.pk is None:
18
+ return None
19
+
20
+ saved = self.__class__.objects.get(pk=self.pk)
21
+
22
+ return {
23
+ field
24
+ for field in self.check_fields
25
+ if getattr(self, field) != getattr(saved, field)
26
+ }
@@ -14,11 +14,12 @@ from netbox_dns.utilities import (
14
14
  NameFormatError,
15
15
  )
16
16
  from netbox_dns.validators import validate_fqdn
17
+ from netbox_dns.mixins import ObjectModificationMixin
17
18
 
18
19
  from .record import Record, RecordTypeChoices
19
20
 
20
21
 
21
- class NameServer(NetBoxModel):
22
+ class NameServer(ObjectModificationMixin, NetBoxModel):
22
23
  name = models.CharField(
23
24
  unique=True,
24
25
  max_length=255,
@@ -79,14 +80,12 @@ class NameServer(NetBoxModel):
79
80
  def save(self, *args, **kwargs):
80
81
  self.full_clean()
81
82
 
82
- name_changed = (
83
- self.pk is not None and self.name != NameServer.objects.get(pk=self.pk).name
84
- )
83
+ changed_fields = self.changed_fields
85
84
 
86
85
  with transaction.atomic():
87
86
  super().save(*args, **kwargs)
88
87
 
89
- if name_changed:
88
+ if changed_fields is not None and "name" in changed_fields:
90
89
  soa_zones = self.zones_soa.all()
91
90
  for soa_zone in soa_zones:
92
91
  soa_zone.update_soa_record()
@@ -22,9 +22,10 @@ from netbox_dns.utilities import (
22
22
  )
23
23
  from netbox_dns.validators import (
24
24
  validate_fqdn,
25
- validate_extended_hostname,
25
+ validate_generic_name,
26
26
  validate_domain_name,
27
27
  )
28
+ from netbox_dns.mixins import ObjectModificationMixin
28
29
 
29
30
  # +
30
31
  # This is a hack designed to break cyclic imports between Record and Zone
@@ -102,7 +103,7 @@ class RecordStatusChoices(ChoiceSet):
102
103
  ]
103
104
 
104
105
 
105
- class Record(NetBoxModel):
106
+ class Record(ObjectModificationMixin, NetBoxModel):
106
107
  ACTIVE_STATUS_LIST = (RecordStatusChoices.STATUS_ACTIVE,)
107
108
 
108
109
  unique_ptr_qs = Q(
@@ -211,9 +212,12 @@ class Record(NetBoxModel):
211
212
 
212
213
  def __str__(self):
213
214
  try:
214
- name = dns_name.from_text(self.fqdn).relativize(dns_name.root).to_unicode()
215
+ fqdn = dns_name.from_text(
216
+ self.name, origin=dns_name.from_text(self.zone.name)
217
+ ).relativize(dns_name.root)
218
+ name = fqdn.to_unicode()
215
219
  except dns_name.IDNAException:
216
- name = self.name
220
+ name = fqdn.to_text()
217
221
  except dns_name.LabelTooLong:
218
222
  name = f"{self.name[:59]}..."
219
223
 
@@ -490,7 +494,7 @@ class Record(NetBoxModel):
490
494
  "netbox_dns", "tolerate_non_rfc1035_types", default=[]
491
495
  ):
492
496
  try:
493
- validate_extended_hostname(
497
+ validate_generic_name(
494
498
  self.name,
495
499
  (
496
500
  self.type
@@ -537,8 +541,7 @@ class Record(NetBoxModel):
537
541
  )
538
542
 
539
543
  case (
540
- RecordTypeChoices.DNAME
541
- | RecordTypeChoices.NS
544
+ RecordTypeChoices.NS
542
545
  | RecordTypeChoices.HTTPS
543
546
  | RecordTypeChoices.SRV
544
547
  | RecordTypeChoices.SVCB
@@ -546,6 +549,12 @@ class Record(NetBoxModel):
546
549
  _validate_idn(rr.target)
547
550
  validate_domain_name(rr.target.to_text(), always_tolerant=True)
548
551
 
552
+ case RecordTypeChoices.DNAME:
553
+ _validate_idn(rr.target)
554
+ validate_domain_name(
555
+ rr.target.to_text(), always_tolerant=True, zone_name=True
556
+ )
557
+
549
558
  case RecordTypeChoices.PTR | RecordTypeChoices.NSAP_PTR:
550
559
  _validate_idn(rr.target)
551
560
  validate_fqdn(rr.target.to_text(), always_tolerant=True)
@@ -566,7 +575,7 @@ class Record(NetBoxModel):
566
575
 
567
576
  case RecordTypeChoices.NAPTR:
568
577
  _validate_idn(rr.replacement)
569
- validate_extended_hostname(
578
+ validate_generic_name(
570
579
  rr.replacement.to_text(), always_tolerant=True
571
580
  )
572
581
 
@@ -783,7 +792,9 @@ class Record(NetBoxModel):
783
792
  self.ptr_record.delete()
784
793
  self.ptr_record = None
785
794
 
786
- super().save(*args, **kwargs)
795
+ changed_fields = self.changed_fields
796
+ if changed_fields is None or changed_fields:
797
+ super().save(*args, **kwargs)
787
798
 
788
799
  _zone = self.zone
789
800
  if self.type != RecordTypeChoices.SOA and _zone.soa_serial_auto:
@@ -7,8 +7,10 @@ from netbox.search import SearchIndex, register_search
7
7
  from netbox.context import current_request
8
8
  from utilities.exceptions import AbortRequest
9
9
 
10
+ from netbox_dns.mixins import ObjectModificationMixin
10
11
 
11
- class View(NetBoxModel):
12
+
13
+ class View(ObjectModificationMixin, NetBoxModel):
12
14
  name = models.CharField(
13
15
  unique=True,
14
16
  max_length=255,
@@ -53,13 +55,11 @@ class View(NetBoxModel):
53
55
  super().delete(*args, **kwargs)
54
56
 
55
57
  def clean(self, *args, old_state=None, **kwargs):
56
- if self.pk is None:
58
+ if (changed_fields := self.changed_fields) is None:
57
59
  return
58
60
 
59
- old_state = View.objects.get(pk=self.pk)
60
-
61
61
  if (
62
- old_state.default_view
62
+ "default_view" in changed_fields
63
63
  and not self.default_view
64
64
  and not View.objects.filter(default_view=True).exclude(pk=self.pk).exists()
65
65
  ):
@@ -74,12 +74,14 @@ class View(NetBoxModel):
74
74
  def save(self, *args, **kwargs):
75
75
  self.clean()
76
76
 
77
- old_state = None if self.pk is None else View.objects.get(pk=self.pk)
77
+ changed_fields = self.changed_fields
78
78
 
79
79
  super().save(*args, **kwargs)
80
80
 
81
- if (old_state is None and self.default_view) or (
82
- old_state is not None and self.default_view and not old_state.default_view
81
+ if (changed_fields is None and self.default_view) or (
82
+ changed_fields is not None
83
+ and self.default_view
84
+ and "default_view" in changed_fields
83
85
  ):
84
86
  other_views = View.objects.filter(default_view=True).exclude(pk=self.pk)
85
87
  for view in other_views:
@@ -1,3 +1,4 @@
1
+ import re
1
2
  from math import ceil
2
3
  from datetime import datetime
3
4
 
@@ -35,6 +36,7 @@ from netbox_dns.validators import (
35
36
  validate_fqdn,
36
37
  validate_domain_name,
37
38
  )
39
+ from netbox_dns.mixins import ObjectModificationMixin
38
40
 
39
41
  # +
40
42
  # This is a hack designed to break cyclic imports between View, Record and Zone
@@ -77,7 +79,7 @@ class ZoneStatusChoices(ChoiceSet):
77
79
  ]
78
80
 
79
81
 
80
- class Zone(NetBoxModel):
82
+ class Zone(ObjectModificationMixin, NetBoxModel):
81
83
  ACTIVE_STATUS_LIST = (ZoneStatusChoices.STATUS_ACTIVE,)
82
84
 
83
85
  view = models.ForeignKey(
@@ -352,6 +354,22 @@ class Zone(NetBoxModel):
352
354
  )
353
355
  )
354
356
 
357
+ @property
358
+ def child_zones(self):
359
+ return Zone.objects.filter(
360
+ name__iregex=rf"^[^.]+\.{re.escape(self.name)}$", view=self.view
361
+ )
362
+
363
+ @property
364
+ def parent_zone(self):
365
+ parent_name = (
366
+ dns_name.from_text(self.name).parent().relativize(dns_name.root).to_text()
367
+ )
368
+ try:
369
+ return Zone.objects.get(name=parent_name, view=self.view)
370
+ except Zone.DoesNotExist:
371
+ return None
372
+
355
373
  def record_count(self, managed=False):
356
374
  return record.Record.objects.filter(zone=self, managed=managed).count()
357
375
 
@@ -592,7 +610,7 @@ class Zone(NetBoxModel):
592
610
  ) from None
593
611
 
594
612
  try:
595
- validate_domain_name(self.name)
613
+ validate_domain_name(self.name, zone_name=True)
596
614
  except ValidationError as exc:
597
615
  raise ValidationError(
598
616
  {
@@ -683,17 +701,7 @@ class Zone(NetBoxModel):
683
701
  def save(self, *args, **kwargs):
684
702
  self.full_clean()
685
703
 
686
- new_zone = self.pk is None
687
- if not new_zone:
688
- old_zone = Zone.objects.get(pk=self.pk)
689
-
690
- name_changed = not new_zone and old_zone.name != self.name
691
- view_changed = not new_zone and old_zone.view != self.view
692
- status_changed = not new_zone and old_zone.status != self.status
693
- rfc2317_changed = not new_zone and (
694
- old_zone.rfc2317_prefix != self.rfc2317_prefix
695
- or old_zone.rfc2317_parent_managed != self.rfc2317_parent_managed
696
- )
704
+ changed_fields = self.changed_fields
697
705
 
698
706
  if self.soa_serial_auto:
699
707
  self.soa_serial = self.get_auto_serial()
@@ -701,7 +709,7 @@ class Zone(NetBoxModel):
701
709
  super().save(*args, **kwargs)
702
710
 
703
711
  if (
704
- new_zone or name_changed or view_changed or status_changed
712
+ changed_fields is None or {"name", "view", "status"} & changed_fields
705
713
  ) and self.is_reverse_zone:
706
714
  zones = Zone.objects.filter(
707
715
  view=self.view,
@@ -733,11 +741,9 @@ class Zone(NetBoxModel):
733
741
  child_zone.update_rfc2317_parent_zone()
734
742
 
735
743
  if (
736
- new_zone
737
- or name_changed
738
- or view_changed
739
- or status_changed
740
- or rfc2317_changed
744
+ changed_fields is None
745
+ or {"name", "view", "status", "rfc2317_prefix", "rfc2317_parent_managed"}
746
+ & changed_fields
741
747
  ) and self.is_rfc2317_zone:
742
748
  zones = Zone.objects.filter(
743
749
  view=self.view,
@@ -763,7 +769,7 @@ class Zone(NetBoxModel):
763
769
 
764
770
  self.update_rfc2317_parent_zone()
765
771
 
766
- elif name_changed or view_changed or status_changed:
772
+ elif changed_fields is not None and {"name", "view", "status"} & changed_fields:
767
773
  for address_record in self.record_set.filter(
768
774
  type__in=(record.RecordTypeChoices.A, record.RecordTypeChoices.AAAA)
769
775
  ):
@@ -772,7 +778,7 @@ class Zone(NetBoxModel):
772
778
  # Fix name in IP Address when zone name is changed
773
779
  if (
774
780
  get_plugin_config("netbox_dns", "feature_ipam_coupling")
775
- and name_changed
781
+ and "name" in changed_fields
776
782
  ):
777
783
  for ip in IPAddress.objects.filter(
778
784
  custom_field_data__ipaddress_dns_zone_id=self.pk
@@ -780,7 +786,7 @@ class Zone(NetBoxModel):
780
786
  ip.dns_name = f'{ip.custom_field_data["ipaddress_dns_record_name"]}.{self.name}'
781
787
  ip.save(update_fields=["dns_name"])
782
788
 
783
- if name_changed:
789
+ if changed_fields is not None and "name" in changed_fields:
784
790
  for _record in self.record_set.all():
785
791
  _record.save(
786
792
  update_fields=["fqdn"],
@@ -1,4 +1,8 @@
1
1
  from netbox.plugins import PluginMenuButton, PluginMenuItem, PluginMenu
2
+ from netbox.plugins.utils import get_plugin_config
3
+
4
+ menu_name = get_plugin_config("netbox_dns", "menu_name")
5
+ top_level_menu = get_plugin_config("netbox_dns", "top_level_menu")
2
6
 
3
7
  view_menu_item = PluginMenuItem(
4
8
  link="plugins:netbox_dns:view_list",
@@ -126,26 +130,38 @@ contact_menu_item = PluginMenuItem(
126
130
  ),
127
131
  )
128
132
 
129
- menu = PluginMenu(
130
- label="NetBox DNS",
131
- groups=(
132
- (
133
- "DNS Configuration",
133
+
134
+ if top_level_menu:
135
+ menu = PluginMenu(
136
+ label=menu_name,
137
+ groups=(
134
138
  (
135
- view_menu_item,
136
- zone_menu_item,
137
- nameserver_menu_item,
138
- record_menu_item,
139
- managed_record_menu_item,
139
+ "DNS Configuration",
140
+ (
141
+ view_menu_item,
142
+ zone_menu_item,
143
+ nameserver_menu_item,
144
+ record_menu_item,
145
+ managed_record_menu_item,
146
+ ),
140
147
  ),
141
- ),
142
- (
143
- "Domain Registration",
144
148
  (
145
- registrar_menu_item,
146
- contact_menu_item,
149
+ "Domain Registration",
150
+ (
151
+ registrar_menu_item,
152
+ contact_menu_item,
153
+ ),
147
154
  ),
148
155
  ),
149
- ),
150
- icon_class="mdi mdi-dns",
151
- )
156
+ icon_class="mdi mdi-dns",
157
+ )
158
+ else:
159
+ menu_items = (
160
+ view_menu_item,
161
+ zone_menu_item,
162
+ nameserver_menu_item,
163
+ record_menu_item,
164
+ managed_record_menu_item,
165
+ registrar_menu_item,
166
+ contact_menu_item,
167
+ )
@@ -10,7 +10,7 @@ class ViewTable(TenancyColumnsMixin, NetBoxTable):
10
10
  name = tables.Column(
11
11
  linkify=True,
12
12
  )
13
- default_view = tables.Column(
13
+ default_view = tables.BooleanColumn(
14
14
  verbose_name="Default View",
15
15
  )
16
16
  tags = TagColumn(url_name="plugins:netbox_dns:view_list")
@@ -94,25 +94,25 @@
94
94
  {% if object.ptr_record %}
95
95
  <tr>
96
96
  <th scope="row">PTR Record</th>
97
- <td><a href="{% url 'plugins:netbox_dns:record' pk=object.ptr_record.pk %}">{{ object.ptr_record }}</td>
97
+ <td>{{ object.ptr_record|linkify }}</td>
98
98
  </tr>
99
99
  {% endif %}
100
100
  {% if object.address_record %}
101
101
  <tr>
102
102
  <th scope="row">Address Record</th>
103
- <td><a href="{% url 'plugins:netbox_dns:record' pk=object.address_record.pk %}">{{ object.address_record }}</td>
103
+ <td>{{ object.address_record|linkify }}</td>
104
104
  </tr>
105
105
  {% if object.address_record.ipam_ip_address %}
106
106
  <tr>
107
107
  <th scope="row">IPAM IP Address</th>
108
- <td><a href="{% url 'ipam:ipaddress' pk=object.address_record.ipam_ip_address.pk %}">{{ object.address_record.ipam_ip_address }}</td>
108
+ <td>{{ object.address_record.ipam_ip_address|linkify }}</td>
109
109
  </tr>
110
110
  {% endif %}
111
111
  {% endif %}
112
112
  {% if object.ipam_ip_address %}
113
113
  <tr>
114
114
  <th scope="row">IPAM IP Address</th>
115
- <td><a href="{% url 'ipam:ipaddress' pk=object.ipam_ip_address.pk %}">{{ object.ipam_ip_address }}</td>
115
+ <td>{{ object.ipam_ip_address|linkify }}</td>
116
116
  </tr>
117
117
  {% endif %}
118
118
  <tr>
@@ -0,0 +1,18 @@
1
+ {% extends 'netbox_dns/zone/base.html' %}
2
+ {% load helpers %}
3
+ {% load render_table from django_tables2 %}
4
+ {% load perms %}
5
+
6
+ {% block content %}
7
+ {% include 'inc/table_controls_htmx.html' with table_modal="ChildZoneTable_config" %}
8
+ <div class="card">
9
+ <div class="htmx-container table-responsive" id="object_list">
10
+ {% include 'htmx/table.html' %}
11
+ </div>
12
+ </div>
13
+ {% endblock %}
14
+
15
+ {% block modals %}
16
+ {{ block.super }}
17
+ {% table_config_form table %}
18
+ {% endblock modals %}
@@ -20,13 +20,15 @@
20
20
  <td>{{ unicode_name }}</td>
21
21
  </tr>
22
22
  {% endif %}
23
+ {% if parent_zone %}
24
+ <tr>
25
+ <th scope="row">Parent Zone</th>
26
+ <td>{{ parent_zone|linkify }}</td>
27
+ </tr>
28
+ {% endif %}
23
29
  <tr>
24
30
  <th scope="row">View</th>
25
- <td>
26
- <a href="{% url 'plugins:netbox_dns:view' pk=object.view.pk %}">
27
- {{ object.view }}
28
- </a>
29
- </td>
31
+ <td>{{ object.view|linkify }}</td>
30
32
  </tr>
31
33
  {% if object.description %}
32
34
  <tr>
@@ -52,13 +54,7 @@
52
54
  <td>
53
55
  <table>
54
56
  {% for nameserver in object.nameservers.all %}
55
- <tr>
56
- <td>
57
- <a href="{% url 'plugins:netbox_dns:nameserver' pk=nameserver.pk %}">
58
- {{ nameserver }}
59
- </a>
60
- </td>
61
- </tr>
57
+ <tr><td>{{ nameserver|linkify }}</td></tr>
62
58
  {% endfor %}
63
59
  </table>
64
60
  </td>
@@ -115,7 +111,7 @@
115
111
  </tr>
116
112
  <tr>
117
113
  <th scope="row">MName</th>
118
- <td><a href="{% url 'plugins:netbox_dns:nameserver' pk=object.soa_mname.pk %}">{{ object.soa_mname }}</a></td>
114
+ <td>{{ object.soa_mname|linkify }}</td>
119
115
  </tr>
120
116
  <tr>
121
117
  <th scope="row">RName</th>
@@ -153,7 +149,7 @@
153
149
  {% if object.rfc2317_prefix %}
154
150
  <div class="card">
155
151
  <h5 class="card-header">RFC2317</h5>
156
- <table class="table table-hover soa-table">
152
+ <table class="table table-hover attr-table">
157
153
  <tr>
158
154
  <th scope="row">Prefix</th>
159
155
  <td>{{ object.rfc2317_prefix }}</td>
@@ -165,7 +161,7 @@
165
161
  {% if object.rfc2317_parent_managed %}
166
162
  <tr>
167
163
  <th scope="row">Parent Zone</th>
168
- <td><a href="{% url 'plugins:netbox_dns:zone' pk=object.rfc2317_parent_zone.pk %}">{{ object.rfc2317_parent_zone }}</a></td>
164
+ <td>{{ object.rfc2317_parent_zone|linkify }}</td>
169
165
  </tr>
170
166
  {% endif %}
171
167
  </table>
@@ -15,6 +15,7 @@ from netbox_dns.views import (
15
15
  ZoneManagedRecordListView,
16
16
  ZoneRegistrationView,
17
17
  ZoneRFC2317ChildZoneListView,
18
+ ZoneChildZoneListView,
18
19
  )
19
20
 
20
21
  zone_urlpatterns = [
@@ -37,6 +38,11 @@ zone_urlpatterns = [
37
38
  ZoneRFC2317ChildZoneListView.as_view(),
38
39
  name="zone_rfc2317_child_zones",
39
40
  ),
41
+ path(
42
+ "zones/<int:pk>/childzones/",
43
+ ZoneChildZoneListView.as_view(),
44
+ name="zone_child_zones",
45
+ ),
40
46
  path(
41
47
  "zones/<int:pk>/registration/",
42
48
  ZoneRegistrationView.as_view(),
@@ -21,7 +21,7 @@ def arpa_to_prefix(arpa_name):
21
21
 
22
22
  try:
23
23
  return IPNetwork(f"{address}/{mask}")
24
- except AddrFormatError:
24
+ except (AddrFormatError, ValueError):
25
25
  return None
26
26
 
27
27
  elif name.endswith("ip6.arpa"):
@@ -0,0 +1,86 @@
1
+ import re
2
+
3
+ from django.core.exceptions import ValidationError
4
+
5
+ from netbox.plugins.utils import get_plugin_config
6
+
7
+ import logging
8
+
9
+
10
+ def _get_label(tolerate_leading_underscores=False, always_tolerant=False):
11
+ tolerate_characters = re.escape(
12
+ get_plugin_config("netbox_dns", "tolerate_characters_in_zone_labels", "")
13
+ )
14
+ label_characters = rf"a-z0-9{tolerate_characters}"
15
+
16
+ if always_tolerant:
17
+ label = rf"[a-z0-9_][a-z0-9_-]*(?<![-_])"
18
+ zone_label = rf"[{label_characters}_][{label_characters}_-]*(?<![-_])"
19
+
20
+ return label, zone_label
21
+
22
+ tolerate_underscores = get_plugin_config(
23
+ "netbox_dns", "tolerate_underscores_in_labels"
24
+ ) or get_plugin_config("netbox_dns", "tolerate_underscores_in_hostnames")
25
+
26
+ if tolerate_leading_underscores:
27
+ if tolerate_underscores:
28
+ label = r"[a-z0-9_][a-z0-9_-]*(?<![-_])"
29
+ zone_label = rf"[{label_characters}_][{label_characters}_-]*(?<![-_])"
30
+ else:
31
+ label = rf"[a-z0-9_][a-z0-9-]*(?<!-)"
32
+ zone_label = rf"[{label_characters}_][{label_characters}-]*(?<!-)"
33
+ elif tolerate_underscores:
34
+ label = rf"[a-z0-9][a-z0-9_-]*(?<![-_])"
35
+ zone_label = rf"[{label_characters}][{label_characters}_-]*(?<![-_])"
36
+ else:
37
+ label = rf"[a-z0-9][a-z0-9-]*(?<!-)"
38
+ zone_label = rf"[{label_characters}][{label_characters}-]*(?<!-)"
39
+
40
+ return label, zone_label
41
+
42
+
43
+ def has_invalid_double_dash(name):
44
+ return bool(re.findall(r"\b(?!xn)..--", name, re.IGNORECASE))
45
+
46
+
47
+ def validate_fqdn(name, always_tolerant=False):
48
+ label, zone_label = _get_label(always_tolerant=always_tolerant)
49
+ regex = rf"^(\*|{label})(\.{zone_label})+\.?$"
50
+
51
+ if not re.match(regex, name, flags=re.IGNORECASE) or has_invalid_double_dash(name):
52
+ raise ValidationError(f"{name} is not a valid fully qualified DNS host name")
53
+
54
+
55
+ def validate_generic_name(
56
+ name, tolerate_leading_underscores=False, always_tolerant=False
57
+ ):
58
+ label, zone_label = _get_label(
59
+ tolerate_leading_underscores=tolerate_leading_underscores,
60
+ always_tolerant=always_tolerant,
61
+ )
62
+ regex = rf"^([*@]|(\*\.)?{label}(\.{zone_label})*\.?)$"
63
+
64
+ if not re.match(regex, name, flags=re.IGNORECASE) or has_invalid_double_dash(name):
65
+ raise ValidationError(f"{name} is not a valid DNS host name")
66
+
67
+
68
+ def validate_domain_name(
69
+ name, always_tolerant=False, allow_empty_label=False, zone_name=False
70
+ ):
71
+ if name == "@" and allow_empty_label:
72
+ return
73
+
74
+ if name == "." and (
75
+ always_tolerant or get_plugin_config("netbox_dns", "enable_root_zones")
76
+ ):
77
+ return
78
+
79
+ label, zone_label = _get_label(always_tolerant=always_tolerant)
80
+ if zone_name:
81
+ regex = rf"^{zone_label}(\.{zone_label})*\.?$"
82
+ else:
83
+ regex = rf"^{label}(\.{zone_label})*\.?$"
84
+
85
+ if not re.match(regex, name, flags=re.IGNORECASE) or has_invalid_double_dash(name):
86
+ raise ValidationError(f"{name} is not a valid DNS domain name")
@@ -40,6 +40,7 @@ class ZoneView(generic.ObjectView):
40
40
  context = {
41
41
  "nameserver_warnings": ns_warnings,
42
42
  "nameserver_errors": ns_errors,
43
+ "parent_zone": instance.parent_zone,
43
44
  }
44
45
 
45
46
  name = dns_name.from_text(instance.name)
@@ -165,3 +166,22 @@ class ZoneRFC2317ChildZoneListView(generic.ObjectChildrenView):
165
166
 
166
167
  def get_children(self, request, parent):
167
168
  return parent.rfc2317_child_zones.all()
169
+
170
+
171
+ @register_model_view(Zone, "child_zones")
172
+ class ZoneChildZoneListView(generic.ObjectChildrenView):
173
+ queryset = Zone.objects.all()
174
+ child_model = Zone
175
+ table = ZoneTable
176
+ filterset = ZoneFilterSet
177
+ template_name = "netbox_dns/zone/child_zone.html"
178
+
179
+ tab = ViewTab(
180
+ label="Child Zones",
181
+ permission="netbox_dns.view_zone",
182
+ badge=lambda obj: obj.child_zones.count(),
183
+ hide_if_empty=True,
184
+ )
185
+
186
+ def get_children(self, request, parent):
187
+ return parent.child_zones
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "netbox-plugin-dns"
3
- version = "1.0.1"
3
+ version = "1.0.3"
4
4
  description = "NetBox DNS is a NetBox plugin for managing DNS data."
5
5
  authors = ["Peter Eckel <pete@netbox-dns.org>"]
6
6
  homepage = "https://github.com/peteeckel/netbox-plugin-dns"
@@ -1,65 +0,0 @@
1
- import re
2
-
3
- from django.core.exceptions import ValidationError
4
-
5
- from netbox.plugins.utils import get_plugin_config
6
-
7
- LABEL = r"[a-z0-9][a-z0-9-]*(?<!-)"
8
- TOLERANT_LABEL = r"[a-z0-9][a-z0-9-_]*(?<![-_])"
9
- LEADING_UNDERSCORE_LABEL = r"[a-z0-9_][a-z0-9-]*(?<!-)"
10
- TOLERANT_LEADING_UNDERSCORE_LABEL = r"[a-z0-9_][a-z0-9-_]*(?<![-_])"
11
-
12
-
13
- def has_invalid_double_dash(name):
14
- return bool(re.findall(r"\b(?!xn)..--", name, re.IGNORECASE))
15
-
16
-
17
- def validate_fqdn(name, always_tolerant=False):
18
- if always_tolerant or get_plugin_config(
19
- "netbox_dns", "tolerate_underscores_in_hostnames"
20
- ):
21
- regex = rf"^(\*|{TOLERANT_LABEL})(\.{TOLERANT_LABEL})+\.?$"
22
- else:
23
- regex = rf"^(\*|{LABEL})(\.{LABEL})+\.?$"
24
-
25
- if not re.match(regex, name, flags=re.IGNORECASE) or has_invalid_double_dash(name):
26
- raise ValidationError(f"{name} is not a valid fully qualified DNS host name")
27
-
28
-
29
- def validate_extended_hostname(
30
- name, tolerate_leading_underscores=False, always_tolerant=False
31
- ):
32
- if always_tolerant or tolerate_leading_underscores:
33
- if always_tolerant or get_plugin_config(
34
- "netbox_dns", "tolerate_underscores_in_hostnames"
35
- ):
36
- regex = rf"^([*@]|(\*\.)?{TOLERANT_LEADING_UNDERSCORE_LABEL}(\.{TOLERANT_LEADING_UNDERSCORE_LABEL})*\.?)$"
37
- else:
38
- regex = rf"^([*@]|(\*\.)?{LEADING_UNDERSCORE_LABEL}(\.{LEADING_UNDERSCORE_LABEL})*\.?)$"
39
- elif get_plugin_config("netbox_dns", "tolerate_underscores_in_hostnames"):
40
- regex = rf"^([*@]|(\*\.)?{TOLERANT_LABEL}(\.{TOLERANT_LABEL})*\.?)$"
41
- else:
42
- regex = rf"^([*@]|(\*\.)?{LABEL}(\.{LABEL})*\.?)$"
43
-
44
- if not re.match(regex, name, flags=re.IGNORECASE) or has_invalid_double_dash(name):
45
- raise ValidationError(f"{name} is not a valid DNS host name")
46
-
47
-
48
- def validate_domain_name(name, always_tolerant=False, allow_empty_label=False):
49
- if name == "@" and allow_empty_label:
50
- return
51
-
52
- if name == "." and (
53
- always_tolerant or get_plugin_config("netbox_dns", "enable_root_zones")
54
- ):
55
- return
56
-
57
- if always_tolerant or get_plugin_config(
58
- "netbox_dns", "tolerate_underscores_in_hostnames"
59
- ):
60
- regex = rf"^{TOLERANT_LABEL}(\.{TOLERANT_LABEL})*\.?$"
61
- else:
62
- regex = rf"^{LABEL}(\.{LABEL})*\.?$"
63
-
64
- if not re.match(regex, name, flags=re.IGNORECASE) or has_invalid_double_dash(name):
65
- raise ValidationError(f"{name} is not a valid DNS domain name")