netbox-ddns 1.4.0__py3-none-any.whl → 1.7.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.
Files changed (34) hide show
  1. netbox_ddns/__init__.py +3 -3
  2. netbox_ddns/admin.py +0 -156
  3. netbox_ddns/api/serializers.py +20 -2
  4. netbox_ddns/api/views.py +1 -0
  5. netbox_ddns/background_tasks.py +28 -5
  6. netbox_ddns/filtersets.py +23 -1
  7. netbox_ddns/forms.py +54 -3
  8. netbox_ddns/migrations/0011_server_created_server_custom_field_data_and_more.py +36 -0
  9. netbox_ddns/migrations/0012_zone_created_zone_custom_field_data_and_more.py +36 -0
  10. netbox_ddns/migrations/0013_reversezone_created_reversezone_custom_field_data_and_more.py +36 -0
  11. netbox_ddns/migrations/0014_alter_extradnsname_name.py +19 -0
  12. netbox_ddns/migrations/0015_server_protocol.py +18 -0
  13. netbox_ddns/models.py +61 -9
  14. netbox_ddns/navigation.py +62 -0
  15. netbox_ddns/tables.py +78 -32
  16. netbox_ddns/template_content.py +40 -3
  17. netbox_ddns/templates/netbox_ddns/extradnsname.html +23 -131
  18. netbox_ddns/templates/netbox_ddns/ipaddress/dns_extra.html +1 -1
  19. netbox_ddns/templates/netbox_ddns/managed_dns_names.html +17 -0
  20. netbox_ddns/templates/netbox_ddns/reversezone.html +51 -0
  21. netbox_ddns/templates/netbox_ddns/server.html +74 -0
  22. netbox_ddns/templates/netbox_ddns/update_reverse_zone.html +11 -0
  23. netbox_ddns/templates/netbox_ddns/update_zone.html +11 -0
  24. netbox_ddns/templates/netbox_ddns/zone.html +53 -0
  25. netbox_ddns/urls.py +32 -14
  26. netbox_ddns/utils.py +95 -0
  27. netbox_ddns/validators.py +6 -0
  28. netbox_ddns/views.py +268 -84
  29. {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info}/METADATA +7 -5
  30. netbox_ddns-1.7.0.dist-info/RECORD +50 -0
  31. {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info}/WHEEL +1 -1
  32. netbox_ddns-1.4.0.dist-info/RECORD +0 -38
  33. {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info/licenses}/LICENSE.txt +0 -0
  34. {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,74 @@
1
+ {% extends 'generic/object.html' %}
2
+ {% load render_table from django_tables2 %}
3
+
4
+ {% block content %}
5
+ <div class="row">
6
+ <div class="col col-md-6">
7
+ <div class="card">
8
+ <h2 class="card-header">Configuration</h2>
9
+ <table class="table table-hover attr-table">
10
+ <tr>
11
+ <th scope="row">Server</th>
12
+ <td>
13
+ {{ object.server }}
14
+ </td>
15
+ </tr>
16
+ <tr>
17
+ <th scope="row">Server port</th>
18
+ <td>
19
+ {{ object.server_port }}
20
+ </td>
21
+ </tr>
22
+ <tr>
23
+ <th scope="row">Protocol</th>
24
+ <td>
25
+ {{ object.protocol }}
26
+ </td>
27
+ </tr>
28
+ </table>
29
+ </div>
30
+ <div class="card">
31
+ <h2 class="card-header">Authentication</h2>
32
+ <table class="table table-hover attr-table">
33
+ <tr>
34
+ <th scope="row">Name</th>
35
+ <td>
36
+ {{ object.tsig_key_name }}
37
+ </td>
38
+ </tr>
39
+ <tr>
40
+ <th scope="row">Algorithm</th>
41
+ <td>
42
+ {{ object.tsig_algorithm }}
43
+ </td>
44
+ </tr>
45
+ <tr>
46
+ <th scope="row">Key</th>
47
+ <td>
48
+ {{ object.tsig_key }}
49
+ </td>
50
+ </tr>
51
+ </table>
52
+ </div>
53
+ <div class="card">
54
+ <div class="justify-content-between card-header">
55
+ <h1 class="card-title">Forward Zones</h1>
56
+ </div>
57
+ <div class="card-body table-responsive">
58
+ {% render_table zone_table %}
59
+ </div>
60
+ </div>
61
+ <div class="card">
62
+ <div class="justify-content-between card-header">
63
+ <h1 class="card-title">Reverse Zones</h1>
64
+ </div>
65
+ <div class="card-body table-responsive">
66
+ {% render_table reversezone_table %}
67
+ </div>
68
+ </div>
69
+ {% include 'inc/panels/custom_fields.html' %}
70
+ {% include 'inc/panels/tags.html' %}
71
+ </div>
72
+ </div>
73
+
74
+ {% endblock content %}
@@ -0,0 +1,11 @@
1
+ {% if perms.ipam.change_ipaddress %}
2
+ <form method="post" class="inline-block">
3
+ {% csrf_token %}
4
+
5
+ <button type="submit" name="_edit"
6
+ formaction="{% url 'plugins:netbox_ddns:reversezone_recreate_record' pk=object.pk %}"
7
+ class="btn btn-secondary">
8
+ <span class="mdi mdi-refresh" aria-hidden="true"></span> Recreate all Records
9
+ </button>
10
+ </form>
11
+ {% endif %}
@@ -0,0 +1,11 @@
1
+ {% if perms.ipam.change_ipaddress %}
2
+ <form method="post" class="inline-block">
3
+ {% csrf_token %}
4
+
5
+ <button type="submit" name="_edit"
6
+ formaction="{% url 'plugins:netbox_ddns:zone_recreate_record' pk=object.pk %}"
7
+ class="btn btn-secondary">
8
+ <span class="mdi mdi-refresh" aria-hidden="true"></span> Recreate all Records
9
+ </button>
10
+ </form>
11
+ {% endif %}
@@ -0,0 +1,53 @@
1
+ {% extends 'generic/object.html' %}
2
+ {% load render_table from django_tables2 %}
3
+
4
+ {% block content %}
5
+ <div class="row">
6
+ <div class="col col-md-6">
7
+ <div class="card">
8
+ <h2 class="card-header">Zone</h2>
9
+ <table class="table table-hover attr-table">
10
+ <tr>
11
+ <th scope="row">Name</th>
12
+ <td>
13
+ {{ object.name }}
14
+ </td>
15
+ </tr>
16
+ <tr>
17
+ <th scope="row">TTL</th>
18
+ <td>
19
+ {{ object.ttl }}
20
+ </td>
21
+ </tr>
22
+ <tr>
23
+ <th scope="row">Server</th>
24
+ <td>
25
+ <a href="{{ object.server.get_absolute_url }}">
26
+ {{ object.server }}
27
+ </a>
28
+ </td>
29
+ </tr>
30
+ </table>
31
+ </div>
32
+ <div class="card">
33
+ <div class="justify-content-between card-header">
34
+ <h1 class="card-title">IP Addresses</h1>
35
+ </div>
36
+ <div class="card-body table-responsive">
37
+ {% render_table ip_address_table %}
38
+ </div>
39
+ </div>
40
+ <div class="card">
41
+ <div class="justify-content-between card-header">
42
+ <h1 class="card-title">Extra DNS Names</h1>
43
+ </div>
44
+ <div class="card-body table-responsive">
45
+ {% render_table extra_dns_name_table %}
46
+ </div>
47
+ </div>
48
+ {% include 'inc/panels/custom_fields.html' %}
49
+ {% include 'inc/panels/tags.html' %}
50
+ </div>
51
+ </div>
52
+
53
+ {% endblock content %}
netbox_ddns/urls.py CHANGED
@@ -1,22 +1,40 @@
1
- from django.urls import path
1
+ from django.urls import path, include
2
2
 
3
- from .views import ExtraDNSNameCreateView, ExtraDNSNameDeleteView, ExtraDNSNameEditView, IPAddressDNSNameRecreateView, ExtraDNSNameView
3
+ from utilities.urls import get_model_urls
4
+ from .views import (
5
+ ExtraDNSNameCreateView,
6
+ IPAddressDNSNameRecreateView,
7
+ ManagedDNSNameListView,
8
+ UpdateForwardZone,
9
+ UpdateReverseZone,
10
+ )
4
11
 
5
12
  urlpatterns = [
13
+ path('managed-dns-names/', ManagedDNSNameListView.as_view(), name='managed_dns_name_list'),
14
+
15
+ path('extra-dns-names/', include(get_model_urls('netbox_ddns', 'extradnsname', detail=False))),
16
+ path('extra-dns-names/<int:pk>/', include(get_model_urls('netbox_ddns', 'extradnsname'))),
17
+
18
+ path('reverse-zones/', include(get_model_urls('netbox_ddns', 'reversezone', detail=False))),
19
+ path('reverse-zones/<int:pk>/', include(get_model_urls('netbox_ddns', 'reversezone'))),
20
+
21
+ path('zones/', include(get_model_urls('netbox_ddns', 'zone', detail=False))),
22
+ path('zones/<int:pk>/', include(get_model_urls('netbox_ddns', 'zone'))),
23
+
24
+ path('servers/', include(get_model_urls('netbox_ddns', 'server', detail=False))),
25
+ path('servers/<int:pk>/', include(get_model_urls('netbox_ddns', 'server'))),
26
+
27
+ path(route='zones/<int:pk>/recreate_records/',
28
+ view=UpdateForwardZone.as_view(),
29
+ name='zone_recreate_record'),
30
+ path(route='reverse-zones/<int:pk>/recreate_records/',
31
+ view=UpdateReverseZone.as_view(),
32
+ name='reversezone_recreate_record'),
6
33
  path(route='ip-addresses/<int:ipaddress_pk>/recreate/',
7
34
  view=IPAddressDNSNameRecreateView.as_view(),
8
35
  name='ipaddress_dnsname_recreate'),
9
- path(route='ip-addresses/<int:ipaddress_pk>/extra/create/',
36
+ path(route='ip-addresses/<int:ipaddress_pk>/extra-dns-name/create/',
10
37
  view=ExtraDNSNameCreateView.as_view(),
11
- name='extradnsname_create'),
12
- path(route='ip-addresses/<int:ipaddress_pk>/extra/<int:pk>/edit/',
13
- view=ExtraDNSNameEditView.as_view(),
14
- name='extradnsname_edit'),
15
- path(route='ip-addresses/<int:ipaddress_pk>/extra/<int:pk>/delete/',
16
- view=ExtraDNSNameDeleteView.as_view(),
17
- name='extradnsname_delete'),
18
-
19
- path(route='ip-addresses/<int:ipaddress_pk>/extra/<int:pk>/',
20
- view=ExtraDNSNameView.as_view(),
21
- name='extradnsname'),
38
+ name='extradnsname_ip_address_create'),
39
+
22
40
  ]
netbox_ddns/utils.py CHANGED
@@ -1,6 +1,101 @@
1
+ from dataclasses import dataclass
2
+ from typing import TYPE_CHECKING, List, Optional
3
+
1
4
  import dns.rdatatype
2
5
  import dns.resolver
3
6
 
7
+ if TYPE_CHECKING:
8
+ from ipam.models import IPAddress
9
+ from netbox_ddns.models import Zone
10
+
11
+
12
+ @dataclass
13
+ class ManagedDNSNameRow:
14
+ """Primary DNS name from IPAddress, with optional zone."""
15
+ dns_name: str
16
+ ip_address: 'IPAddress'
17
+ zone: Optional['Zone']
18
+ obj: 'IPAddress'
19
+ view_url: str = ''
20
+ edit_url: str = ''
21
+ delete_url: Optional[str] = None
22
+ forward_status_html: str = ''
23
+
24
+
25
+ def _find_zone_for_dns_name(dns_name: str, zones_by_name: dict) -> Optional['Zone']:
26
+ """In-memory zone lookup."""
27
+ parts = dns_name.lower().rstrip('.').split('.')
28
+ for i in range(len(parts)):
29
+ candidate = '.'.join(parts[i:])
30
+ for suffix in (candidate + '.', candidate):
31
+ if suffix in zones_by_name:
32
+ return zones_by_name[suffix]
33
+ return None
34
+
35
+
36
+ def get_managed_dns_names(user) -> List[ManagedDNSNameRow]:
37
+ """
38
+ Return all primary DNS names (from IPAddress.dns_name), with or without a zone.
39
+ Extra DNS names are shown in the separate Extra DNS Names view.
40
+ """
41
+ from django.urls import reverse
42
+
43
+ from dns import rcode
44
+
45
+ from ipam.models import IPAddress
46
+ from netbox_ddns.models import ACTION_CHOICES, DNSStatus, Zone, get_rcode_display
47
+
48
+ rows: List[ManagedDNSNameRow] = []
49
+ no_zone_status = '<span class="text-muted">No zone configured</span>'
50
+
51
+ zones = list(Zone.objects.all().restrict(user, 'view'))
52
+ zones_by_name = {z.name: z for z in zones}
53
+ for z in zones:
54
+ n = z.name.rstrip('.')
55
+ if n != z.name:
56
+ zones_by_name[n] = z
57
+
58
+ ip_addresses = (
59
+ IPAddress.objects.filter(dns_name__isnull=False)
60
+ .exclude(dns_name='')
61
+ .restrict(user, 'view')
62
+ .prefetch_related('dnsstatus')
63
+ .order_by('dns_name')
64
+ )
65
+
66
+ for ip_address in ip_addresses:
67
+ zone = _find_zone_for_dns_name(ip_address.dns_name, zones_by_name)
68
+ if zone is None:
69
+ forward_status = no_zone_status
70
+ else:
71
+ try:
72
+ status = ip_address.dnsstatus
73
+ if status.forward_action is not None:
74
+ output = next(
75
+ label for value, label in ACTION_CHOICES if value == status.forward_action
76
+ )
77
+ output += ': '
78
+ output += get_rcode_display(status.forward_rcode) or ''
79
+ colour = 'green' if status.forward_rcode == rcode.NOERROR else 'red'
80
+ forward_status = f'<span style="color:{colour}">{output}</span>'
81
+ else:
82
+ forward_status = '<span class="text-muted">Not created</span>'
83
+ except DNSStatus.DoesNotExist:
84
+ forward_status = '<span class="text-muted">Not created</span>'
85
+
86
+ rows.append(ManagedDNSNameRow(
87
+ dns_name=ip_address.dns_name,
88
+ ip_address=ip_address,
89
+ zone=zone,
90
+ obj=ip_address,
91
+ view_url=ip_address.get_absolute_url(),
92
+ edit_url=reverse('ipam:ipaddress_edit', args=[ip_address.pk]),
93
+ delete_url=None,
94
+ forward_status_html=forward_status,
95
+ ))
96
+
97
+ return rows
98
+
4
99
 
5
100
  def normalize_fqdn(dns_name: str) -> str:
6
101
  if not dns_name:
netbox_ddns/validators.py CHANGED
@@ -21,3 +21,9 @@ def validate_base64(value):
21
21
  base64.b64decode(value, validate=True)
22
22
  except binascii.Error:
23
23
  raise ValidationError('Invalid base64 string')
24
+
25
+ class DNSNameValidator(RegexValidator):
26
+ regex = r"^([0-9A-Za-z_-]+|\*)(\.[0-9A-Za-z_-]+)*\.?$"
27
+ message = _(
28
+ "Only alphanumeric characters, asterisks, hyphens, periods, and underscores are allowed in DNS names"
29
+ )