netbox-plugin-dns 1.0.7__py3-none-any.whl → 1.1.0b1__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.

Files changed (69) hide show
  1. netbox_dns/__init__.py +21 -5
  2. netbox_dns/api/nested_serializers.py +16 -17
  3. netbox_dns/api/serializers.py +1 -0
  4. netbox_dns/api/serializers_/prefix.py +18 -0
  5. netbox_dns/api/serializers_/record.py +0 -1
  6. netbox_dns/api/serializers_/view.py +34 -2
  7. netbox_dns/api/urls.py +3 -0
  8. netbox_dns/api/views.py +36 -0
  9. netbox_dns/fields/__init__.py +1 -0
  10. netbox_dns/fields/ipam.py +18 -0
  11. netbox_dns/filtersets/record.py +1 -1
  12. netbox_dns/filtersets/view.py +16 -0
  13. netbox_dns/forms/view.py +116 -4
  14. netbox_dns/forms/zone.py +0 -8
  15. netbox_dns/graphql/schema.py +40 -16
  16. netbox_dns/graphql/types.py +1 -0
  17. netbox_dns/management/commands/setup_autodns.py +120 -0
  18. netbox_dns/migrations/0007_view_prefixes.py +18 -0
  19. netbox_dns/models/__init__.py +0 -2
  20. netbox_dns/models/contact.py +3 -9
  21. netbox_dns/models/nameserver.py +3 -8
  22. netbox_dns/models/record.py +71 -17
  23. netbox_dns/models/record_template.py +1 -4
  24. netbox_dns/models/registrar.py +1 -7
  25. netbox_dns/models/view.py +7 -9
  26. netbox_dns/models/zone.py +28 -29
  27. netbox_dns/models/zone_template.py +5 -8
  28. netbox_dns/signals/ipam_autodns.py +138 -0
  29. netbox_dns/tables/contact.py +1 -0
  30. netbox_dns/tables/nameserver.py +7 -1
  31. netbox_dns/tables/record.py +30 -10
  32. netbox_dns/tables/record_template.py +17 -0
  33. netbox_dns/tables/registrar.py +2 -0
  34. netbox_dns/tables/view.py +32 -3
  35. netbox_dns/tables/zone.py +15 -0
  36. netbox_dns/tables/zone_template.py +16 -2
  37. netbox_dns/template_content.py +28 -39
  38. netbox_dns/templates/netbox_dns/record.html +6 -6
  39. netbox_dns/templates/netbox_dns/view/related.html +17 -0
  40. netbox_dns/templates/netbox_dns/view.html +29 -0
  41. netbox_dns/urls/contact.py +32 -10
  42. netbox_dns/urls/nameserver.py +38 -14
  43. netbox_dns/urls/record.py +19 -7
  44. netbox_dns/urls/record_template.py +27 -18
  45. netbox_dns/urls/registrar.py +35 -11
  46. netbox_dns/urls/view.py +22 -8
  47. netbox_dns/urls/zone.py +46 -8
  48. netbox_dns/urls/zone_template.py +26 -16
  49. netbox_dns/utilities/__init__.py +2 -74
  50. netbox_dns/utilities/conversions.py +83 -0
  51. netbox_dns/utilities/ipam_autodns.py +205 -0
  52. netbox_dns/validators/dns_name.py +0 -9
  53. netbox_dns/views/contact.py +1 -0
  54. netbox_dns/views/nameserver.py +3 -7
  55. netbox_dns/views/record.py +2 -9
  56. netbox_dns/views/record_template.py +1 -1
  57. netbox_dns/views/registrar.py +1 -0
  58. netbox_dns/views/view.py +1 -6
  59. netbox_dns/views/zone.py +6 -7
  60. netbox_dns/views/zone_template.py +2 -2
  61. {netbox_plugin_dns-1.0.7.dist-info → netbox_plugin_dns-1.1.0b1.dist-info}/METADATA +2 -2
  62. {netbox_plugin_dns-1.0.7.dist-info → netbox_plugin_dns-1.1.0b1.dist-info}/RECORD +64 -61
  63. netbox_dns/management/commands/setup_coupling.py +0 -109
  64. netbox_dns/migrations/0007_alter_ordering_options.py +0 -25
  65. netbox_dns/signals/ipam_coupling.py +0 -168
  66. netbox_dns/templates/netbox_dns/related_dns_objects.html +0 -21
  67. netbox_dns/utilities/ipam_coupling.py +0 -112
  68. {netbox_plugin_dns-1.0.7.dist-info → netbox_plugin_dns-1.1.0b1.dist-info}/LICENSE +0 -0
  69. {netbox_plugin_dns-1.0.7.dist-info → netbox_plugin_dns-1.1.0b1.dist-info}/WHEEL +0 -0
@@ -1,109 +0,0 @@
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
- from netbox_dns.models import Zone
8
-
9
-
10
- class Command(BaseCommand):
11
- help = "Setup IPAddress custom fields needed for IPAM-DNS coupling"
12
-
13
- def add_arguments(self, parser):
14
- parser.add_argument(
15
- "--remove", action="store_true", help="Remove custom fields"
16
- )
17
- parser.add_argument("--verbose", action="store_true", help="Verbose output")
18
-
19
- def handle(self, *model_names, **options):
20
- ipaddress_object_type = ObjectType.objects.get_for_model(IPAddress)
21
-
22
- if options["remove"]:
23
- for cf in (
24
- "ipaddress_dns_record_name",
25
- "ipaddress_dns_record_ttl",
26
- "ipaddress_dns_record_disable_ptr",
27
- "ipaddress_dns_zone_id",
28
- ):
29
- try:
30
- CustomField.objects.get(
31
- name=cf, object_types=ipaddress_object_type
32
- ).delete()
33
- if options.get("verbose"):
34
- self.stdout.write(f"Custom field '{cf}' removed")
35
- except CustomField.DoesNotExist:
36
- pass
37
-
38
- else:
39
- if not CustomField.objects.filter(
40
- name="ipaddress_dns_record_name",
41
- type=CustomFieldTypeChoices.TYPE_TEXT,
42
- object_types=ipaddress_object_type,
43
- ).exists():
44
- cf_name = CustomField.objects.create(
45
- name="ipaddress_dns_record_name",
46
- label="Name",
47
- type=CustomFieldTypeChoices.TYPE_TEXT,
48
- required=False,
49
- group_name="DNS",
50
- )
51
- cf_name.object_types.set([ipaddress_object_type])
52
- if options.get("verbose"):
53
- self.stdout.write(
54
- "Created custom field 'ipaddress_dns_record_name'"
55
- )
56
-
57
- if not CustomField.objects.filter(
58
- name="ipaddress_dns_record_ttl",
59
- type=CustomFieldTypeChoices.TYPE_INTEGER,
60
- object_types=ipaddress_object_type,
61
- ).exists():
62
- cf_ttl = CustomField.objects.create(
63
- name="ipaddress_dns_record_ttl",
64
- label="TTL",
65
- type=CustomFieldTypeChoices.TYPE_INTEGER,
66
- validation_minimum=0,
67
- validation_maximum=2147483647,
68
- required=False,
69
- group_name="DNS",
70
- )
71
- cf_ttl.object_types.set([ipaddress_object_type])
72
- if options.get("verbose"):
73
- self.stdout.write("Created custom field 'ipaddress_dns_record_ttl'")
74
-
75
- if not CustomField.objects.filter(
76
- name="ipaddress_dns_record_disable_ptr",
77
- type=CustomFieldTypeChoices.TYPE_BOOLEAN,
78
- object_types=ipaddress_object_type,
79
- ).exists():
80
- cf_disable_ptr = CustomField.objects.create(
81
- name="ipaddress_dns_record_disable_ptr",
82
- label="Disable PTR",
83
- type=CustomFieldTypeChoices.TYPE_BOOLEAN,
84
- required=False,
85
- default=False,
86
- group_name="DNS",
87
- )
88
- cf_disable_ptr.object_types.set([ipaddress_object_type])
89
- if options.get("verbose"):
90
- self.stdout.write(
91
- "Created custom field 'ipaddress_dns_record_disable_ptr'"
92
- )
93
-
94
- if not CustomField.objects.filter(
95
- name="ipaddress_dns_zone_id",
96
- type=CustomFieldTypeChoices.TYPE_OBJECT,
97
- object_types=ipaddress_object_type,
98
- ).exists():
99
- cf_zone = CustomField.objects.create(
100
- name="ipaddress_dns_zone_id",
101
- label="Zone",
102
- type=CustomFieldTypeChoices.TYPE_OBJECT,
103
- related_object_type=ObjectType.objects.get_for_model(Zone),
104
- required=False,
105
- group_name="DNS",
106
- )
107
- cf_zone.object_types.set([ipaddress_object_type])
108
- if options.get("verbose"):
109
- self.stdout.write("Created custom field 'ipaddress_dns_zone_id'")
@@ -1,25 +0,0 @@
1
- # Generated by Django 5.0.7 on 2024-08-27 09:29
2
-
3
- from django.db import migrations
4
-
5
-
6
- class Migration(migrations.Migration):
7
-
8
- dependencies = [
9
- ("netbox_dns", "0006_templating"),
10
- ]
11
-
12
- operations = [
13
- migrations.AlterModelOptions(
14
- name="record",
15
- options={"ordering": ("fqdn", "zone", "name", "type", "value", "status")},
16
- ),
17
- migrations.AlterModelOptions(
18
- name="recordtemplate",
19
- options={"ordering": ("name",)},
20
- ),
21
- migrations.AlterModelOptions(
22
- name="zonetemplate",
23
- options={"ordering": ("name",)},
24
- ),
25
- ]
@@ -1,168 +0,0 @@
1
- from django.dispatch import receiver
2
- from django.db.models.signals import pre_save, post_save, pre_delete
3
- from django.core.exceptions import ValidationError, PermissionDenied
4
- from rest_framework.exceptions import PermissionDenied as APIPermissionDenied
5
-
6
- from netbox.signals import post_clean
7
- from netbox.context import current_request
8
- from netbox.plugins.utils import get_plugin_config
9
- from ipam.models import IPAddress
10
-
11
- from netbox_dns.models import Zone
12
- from netbox_dns.utilities.ipam_coupling import (
13
- ipaddress_cf_data,
14
- get_address_record,
15
- new_address_record,
16
- update_address_record,
17
- check_permission,
18
- dns_changed,
19
- DNSPermissionDenied,
20
- )
21
-
22
-
23
- @receiver(post_clean, sender=IPAddress)
24
- def ip_address_check_permissions_save(instance, **kwargs):
25
- if not instance.address:
26
- return
27
-
28
- if not get_plugin_config("netbox_dns", "feature_ipam_coupling"):
29
- return
30
-
31
- request = current_request.get()
32
- if request is None:
33
- return
34
-
35
- try:
36
- if instance.pk is None:
37
- record = new_address_record(instance)
38
- if record is not None:
39
- record.full_clean()
40
- check_permission(request, "netbox_dns.add_record", record)
41
-
42
- else:
43
- if not dns_changed(IPAddress.objects.get(pk=instance.pk), instance):
44
- return
45
-
46
- record = get_address_record(instance)
47
- if record is not None:
48
- name, ttl, disable_ptr, zone_id = ipaddress_cf_data(instance)
49
- if zone_id is not None:
50
- update_address_record(record, instance)
51
- record.full_clean()
52
- check_permission(request, "netbox_dns.change_record", record)
53
- else:
54
- check_permission(request, "netbox_dns.delete_record", record)
55
-
56
- else:
57
- record = new_address_record(instance)
58
- if record is not None:
59
- record.full_clean()
60
- check_permission(request, "netbox_dns.add_record", record)
61
-
62
- except ValidationError as exc:
63
- if hasattr(exc, "error_dict"):
64
- value = exc.error_dict.pop("name", None)
65
- if value is not None:
66
- exc.error_dict["cf_ipaddress_dns_record_name"] = value
67
-
68
- value = exc.error_dict.pop("ttl", None)
69
- if value is not None:
70
- exc.error_dict["cf_ipaddress_dns_record_ttl"] = value
71
-
72
- value = exc.error_dict.pop("value", None)
73
- if value is not None:
74
- exc.error_dict["cf_ipaddress_dns_record_name"] = value
75
-
76
- value = exc.error_dict.pop("type", None)
77
- if value is not None:
78
- exc.error_dict["cf_ipaddress_dns_record_name"] = value
79
-
80
- raise ValidationError(exc)
81
-
82
- except DNSPermissionDenied as exc:
83
- raise ValidationError(exc)
84
-
85
-
86
- @receiver(pre_delete, sender=IPAddress)
87
- def ip_address_delete_address_record(instance, **kwargs):
88
- if not get_plugin_config("netbox_dns", "feature_ipam_coupling"):
89
- return
90
-
91
- request = current_request.get()
92
- if request is not None:
93
- try:
94
- for record in instance.netbox_dns_records.all():
95
- check_permission(request, "netbox_dns.delete_record", record)
96
-
97
- except DNSPermissionDenied as exc:
98
- if request.path_info.startswith("/api/"):
99
- raise APIPermissionDenied(exc) from None
100
-
101
- raise PermissionDenied(exc) from None
102
-
103
- for record in instance.netbox_dns_records.all():
104
- record.delete()
105
-
106
-
107
- #
108
- # Update DNS related fields according to the contents of the IPAM-DNS
109
- # coupling custom fields.
110
- #
111
- @receiver(pre_save, sender=IPAddress)
112
- def ip_address_update_dns_information(instance, **kwargs):
113
- if not get_plugin_config("netbox_dns", "feature_ipam_coupling"):
114
- return
115
-
116
- name, ttl, disable_ptr, zone_id = ipaddress_cf_data(instance)
117
-
118
- previous_zone_id = None
119
- if instance.pk is not None:
120
- try:
121
- old_instance = IPAddress.objects.get(pk=instance.pk)
122
- previous_zone_id = old_instance.custom_field_data.get(
123
- "ipaddress_dns_zone_id"
124
- )
125
- except IPAddress.DoesNotExist:
126
- pass
127
-
128
- if zone_id is not None:
129
- instance.dns_name = f"{name}.{Zone.objects.get(pk=zone_id).name}"
130
- else:
131
- instance.custom_field_data["ipaddress_dns_record_name"] = None
132
- instance.custom_field_data["ipaddress_dns_record_ttl"] = None
133
- instance.custom_field_data["ipaddress_dns_record_disable_ptr"] = False
134
- instance.custom_field_data["ipaddress_dns_zone_id"] = None
135
-
136
- if previous_zone_id is not None:
137
- instance.dns_name = ""
138
-
139
-
140
- #
141
- # Handle DNS record operation after IPAddress has been created or modified
142
- #
143
- @receiver(post_save, sender=IPAddress)
144
- def ip_address_update_address_record(instance, **kwargs):
145
- if not get_plugin_config("netbox_dns", "feature_ipam_coupling"):
146
- return
147
-
148
- name, ttl, disable_ptr, zone_id = ipaddress_cf_data(instance)
149
-
150
- if zone_id is None:
151
- #
152
- # Name/Zone CF data has been removed: Remove the DNS address record
153
- #
154
- for record in instance.netbox_dns_records.all():
155
- record.delete()
156
-
157
- else:
158
- #
159
- # Name/Zone CF data is present: Check for a DNS address record and add
160
- # or modify it as necessary
161
- #
162
- record = get_address_record(instance)
163
- if record is None:
164
- record = new_address_record(instance)
165
- else:
166
- update_address_record(record, instance)
167
-
168
- record.save()
@@ -1,21 +0,0 @@
1
- {% load helpers %}
2
-
3
- <div class="card">
4
- <h5 class="card-header">Related NetBox DNS Objects</h5>
5
- <ul class="list-group list-group-flush">
6
- {% for qs, filter_param in related_dns_models %}
7
- {% with viewname=qs.model|viewname:"list" %}
8
- <a href="{% url viewname %}?{{ filter_param }}={{ object.pk }}" class="list-group-item list-group-item-action d-flex justify-content-between">
9
- {{ qs.model|meta:"verbose_name_plural"|bettertitle }}
10
- {% with count=qs.count %}
11
- {% if count %}
12
- <span class="badge bg-primary rounded-pill">{{ count }}</span>
13
- {% else %}
14
- <span class="badge bg-light rounded-pill">&mdash;</span>
15
- {% endif %}
16
- {% endwith %}
17
- </a>
18
- {% endwith %}
19
- {% endfor %}
20
- </ul>
21
- </div>
@@ -1,112 +0,0 @@
1
- from ipam.choices import IPAddressStatusChoices
2
- from utilities.permissions import resolve_permission
3
-
4
- from netbox_dns.models import Record
5
- from netbox_dns.choices import RecordTypeChoices, RecordStatusChoices
6
-
7
- from netbox.plugins.utils import get_plugin_config
8
-
9
-
10
- class DNSPermissionDenied(Exception):
11
- pass
12
-
13
-
14
- def ipaddress_cf_data(ip_address):
15
- name = ip_address.custom_field_data.get("ipaddress_dns_record_name")
16
- ttl = ip_address.custom_field_data.get("ipaddress_dns_record_ttl")
17
- disable_ptr = ip_address.custom_field_data.get("ipaddress_dns_record_disable_ptr")
18
- if disable_ptr is None:
19
- disable_ptr = False
20
- zone_id = ip_address.custom_field_data.get("ipaddress_dns_zone_id")
21
-
22
- if name is None or zone_id is None:
23
- return None, None, False, None
24
-
25
- return name, ttl, disable_ptr, zone_id
26
-
27
-
28
- def address_record_type(ip_address):
29
- return RecordTypeChoices.AAAA if ip_address.family == 6 else RecordTypeChoices.A
30
-
31
-
32
- def address_record_status(ip_address):
33
- ip_active_status_list = get_plugin_config(
34
- "netbox_dns",
35
- "ipam_coupling_ip_active_status_list",
36
- (
37
- IPAddressStatusChoices.STATUS_ACTIVE,
38
- IPAddressStatusChoices.STATUS_DHCP,
39
- IPAddressStatusChoices.STATUS_SLAAC,
40
- ),
41
- )
42
-
43
- return (
44
- RecordStatusChoices.STATUS_ACTIVE
45
- if ip_address.status in ip_active_status_list
46
- else RecordStatusChoices.STATUS_INACTIVE
47
- )
48
-
49
-
50
- def get_address_record(ip_address):
51
- return ip_address.netbox_dns_records.first()
52
-
53
-
54
- def new_address_record(instance):
55
- name, ttl, disable_ptr, zone_id = ipaddress_cf_data(instance)
56
-
57
- if zone_id is None:
58
- return None
59
-
60
- return Record(
61
- name=name,
62
- zone_id=zone_id,
63
- ttl=ttl,
64
- disable_ptr=disable_ptr,
65
- status=address_record_status(instance),
66
- type=address_record_type(instance),
67
- value=str(instance.address.ip),
68
- ipam_ip_address_id=instance.pk,
69
- managed=True,
70
- )
71
-
72
-
73
- def update_address_record(record, ip_address):
74
- name, ttl, disable_ptr, zone_id = ipaddress_cf_data(ip_address)
75
-
76
- record.name = name
77
- record.ttl = ttl
78
- record.disable_ptr = disable_ptr
79
- record.zone_id = zone_id
80
- record.status = address_record_status(ip_address)
81
- record.value = str(ip_address.address.ip)
82
-
83
-
84
- def check_permission(request, permission, record=None):
85
- if record is not None and record.pk is None:
86
- check_record = None
87
- else:
88
- check_record = record
89
-
90
- user = request.user
91
-
92
- if not user.has_perm(permission, check_record):
93
- action = resolve_permission(permission)[1]
94
- item = "records" if check_record is None else f"record {check_record}"
95
-
96
- raise DNSPermissionDenied(f"User {user} is not allowed to {action} DNS {item}")
97
-
98
-
99
- def dns_changed(old, new):
100
- return any(
101
- (
102
- old.address.ip != new.address.ip,
103
- old.custom_field_data.get("ipaddress_dns_record_name")
104
- != new.custom_field_data.get("ipaddress_dns_record_name"),
105
- old.custom_field_data.get("ipaddress_dns_record_ttl")
106
- != new.custom_field_data.get("ipaddress_dns_record_ttl"),
107
- old.custom_field_data.get("ipaddress_dns_record_disable_ptr")
108
- != new.custom_field_data.get("ipaddress_dns_record_disable_ptr"),
109
- old.custom_field_data.get("ipaddress_dns_zone_id")
110
- != new.custom_field_data.get("ipaddress_dns_zone_id"),
111
- )
112
- )