From 20064ec9bd2fc2af7a706d589ec24460431ecbcc Mon Sep 17 00:00:00 2001 From: Sven Luthi Date: Thu, 11 Dec 2025 15:59:21 +0100 Subject: [PATCH] modified: src/netbox_plugin_bind_provisioner/__init__.py new file: src/netbox_plugin_bind_provisioner/management/commands/export-all-dns-zones.py new file: src/netbox_plugin_bind_provisioner/management/commands/export-dns-zone.py new file: src/netbox_plugin_bind_provisioner/utils.py Adds export commands for zones to bind zone files and bump version to v1.0.4 --- .../__init__.py | 2 +- .../commands/export-all-dns-zones.py | 47 ++++++++++++++++ .../management/commands/export-dns-zone.py | 56 +++++++++++++++++++ src/netbox_plugin_bind_provisioner/utils.py | 35 ++++++++++++ 4 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 src/netbox_plugin_bind_provisioner/management/commands/export-all-dns-zones.py create mode 100644 src/netbox_plugin_bind_provisioner/management/commands/export-dns-zone.py create mode 100644 src/netbox_plugin_bind_provisioner/utils.py diff --git a/src/netbox_plugin_bind_provisioner/__init__.py b/src/netbox_plugin_bind_provisioner/__init__.py index c1d2f99..dd21075 100644 --- a/src/netbox_plugin_bind_provisioner/__init__.py +++ b/src/netbox_plugin_bind_provisioner/__init__.py @@ -5,7 +5,7 @@ from django.utils.translation import gettext_lazy as _ from django.dispatch import receiver from django.conf import settings -__version__ = "1.0.3" +__version__ = "1.0.4" logger = logging.getLogger(__name__) diff --git a/src/netbox_plugin_bind_provisioner/management/commands/export-all-dns-zones.py b/src/netbox_plugin_bind_provisioner/management/commands/export-all-dns-zones.py new file mode 100644 index 0000000..b97250a --- /dev/null +++ b/src/netbox_plugin_bind_provisioner/management/commands/export-all-dns-zones.py @@ -0,0 +1,47 @@ +import os +import dns.zone +import dns.rdatatype +import dns.rdataclass +import dns.exception +import netbox_dns.models +from netbox_plugin_bind_provisioner.utils import export_bind_zone_file +from django.core.management.base import BaseCommand, CommandError + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument( + "--path", type=str, help="Path to the directory where zone files are to be written" + ) + + def handle(self, *args, **options): + # Load parameters + base_path = options['path'] + + if not base_path: + raise CommandError("No --path parameter given") + + # Ensure base_path exists + os.makedirs(base_path, exist_ok=True) + + # Iterate through all zones + nb_zones = netbox_dns.models.Zone.objects.prefetch_related("view").all() + if not nb_zones.exists(): + self.stdout.write(self.style.WARNING("No zones found in NetBox DNS.")) + return + + for zone in nb_zones: + # Determine view directory + view_name = zone.view.name + view_dir = os.path.join(base_path, view_name) + os.makedirs(view_dir, exist_ok=True) + + # Zone file path + file_path = os.path.join(view_dir, zone.name) + + try: + # Call existing export function + export_bind_zone_file(zone, file_path=file_path) + self.stdout.write(self.style.SUCCESS(f"Exported zone '{zone.name}' to '{file_path}'")) + except Exception as e: + self.stderr.write(f"Failed to export zone '{zone.name}': {e}") diff --git a/src/netbox_plugin_bind_provisioner/management/commands/export-dns-zone.py b/src/netbox_plugin_bind_provisioner/management/commands/export-dns-zone.py new file mode 100644 index 0000000..72a4944 --- /dev/null +++ b/src/netbox_plugin_bind_provisioner/management/commands/export-dns-zone.py @@ -0,0 +1,56 @@ +import os +import dns.zone +import dns.rdatatype +import dns.rdataclass +import dns.exception +import netbox_dns.models +from netbox_plugin_bind_provisioner.utils import export_bind_zone_file +from django.core.management.base import BaseCommand, CommandError + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument( + "--view", type=str, help="The name of the view the Zone to be exported is in" + ) + parser.add_argument( + "--zone", type=str, help="The FQDN of the Zone to be exported" + ) + parser.add_argument( + "--file", type=str, help="Path of the zone file to be written" + ) + + + def handle(self, *args, **options): + #if len(options) < 2: + # print("export-zone ") + # sys.exit(1) + + # Load parameters + view_name = options['view'] + zone_name = options['zone'] + file_path = options['file'] + + if not view_name: + raise CommandError("No --view parameter given") + elif not zone_name: + raise CommandError("No --zone parameter given") + elif not file_path: + raise CommandError("No --file parameter given") + + try: + # Load the zone from NetBox DNS + nb_zone = netbox_dns.models.Zone.objects.get(view__name=view_name, name=zone_name) + + if nb_zone: + export_bind_zone_file(nb_zone, file_path=file_path) + else: + print("Zone not found in Netbox. Aborting") + sys.exit(1) + + except Exception as e: + raise CommandError(f"Failed to export zone: {e}") + + self.stdout.write(self.style.SUCCESS(f"Zone '{zone_name}' exported to '{file_path}'")) + + diff --git a/src/netbox_plugin_bind_provisioner/utils.py b/src/netbox_plugin_bind_provisioner/utils.py new file mode 100644 index 0000000..643af44 --- /dev/null +++ b/src/netbox_plugin_bind_provisioner/utils.py @@ -0,0 +1,35 @@ +import os +import dns.zone +import dns.rdatatype +import dns.rdataclass +import dns.exception +import netbox_dns.models + +def export_bind_zone_file(nb_zone: netbox_dns.models.Zone, file_path: str): + + # Create dnspython zone object + dp_zone = dns.zone.Zone(origin=dns.name.from_text(nb_zone.name)) + + for record in nb_zone.records.all(): + rname = record.name or "@" + rdclass = dns.rdataclass.IN + rdatatype = dns.rdatatype.from_text(record.type) + + # Create rdata object + rdata = dns.rdata.from_text(rdclass, rdatatype, record.value) + + # Create rdataset + rdataset = dns.rdataset.Rdataset(rdclass, rdatatype, record.ttl) + rdataset.add(rdata) + + # Add to zone + node = dp_zone.find_node(rname, create=True) + node.rdatasets.append(rdataset) + + # Write zone to file in BIND format + try: + with open(file_path, "w") as f: + dp_zone.to_file(f, sorted=True) + except IOError as e: + raise IOError(f"Failed to write zone file to {file_path}: {e}") +