Browse Source

feat(api): add sync-to-pdns management command

Peter Thomassen 5 years ago
parent
commit
f923e50df7
1 changed files with 72 additions and 0 deletions
  1. 72 0
      api/desecapi/management/commands/sync-to-pdns.py

+ 72 - 0
api/desecapi/management/commands/sync-to-pdns.py

@@ -0,0 +1,72 @@
+from django.core.management import BaseCommand, CommandError, call_command
+from django.db import transaction
+
+from desecapi import pdns
+from desecapi.exceptions import PDNSException
+from desecapi.models import Domain
+from desecapi.pdns_change_tracker import PDNSChangeTracker
+
+
+class Command(BaseCommand):
+    help = 'Sync RRsets from local API database to pdns.'
+
+    def add_arguments(self, parser):
+        parser.add_argument('domain-name', nargs='*',
+                            help='Domain name to sync. If omitted, will import all API domains.')
+
+    def handle(self, *args, **options):
+        domains = Domain.objects.all()
+
+        if options['domain-name']:
+            domains = domains.filter(name__in=options['domain-name'])
+            domain_names = domains.values_list('name', flat=True)
+
+            for domain_name in options['domain-name']:
+                if domain_name not in domain_names:
+                    raise CommandError('{} is not a known domain'.format(domain_name))
+
+        catalog_alignment = False
+        for domain in domains:
+            self.stdout.write('%s ...' % domain.name, ending='')
+            try:
+                created = self._sync_domain(domain)
+                if created:
+                    self.stdout.write(f' created (was missing) ...', ending='')
+                    catalog_alignment = True
+                self.stdout.write(' synced')
+            except Exception as e:
+                self.stdout.write(' failed')
+                msg = 'Error while processing {}: {}'.format(domain.name, e)
+                raise CommandError(msg)
+
+        if catalog_alignment:
+            call_command('align-catalog-zone')
+
+    @staticmethod
+    @transaction.atomic
+    def _sync_domain(domain):
+        created = False
+
+        # Create domain on pdns if it does not exist
+        try:
+            PDNSChangeTracker.CreateDomain(domain_name=domain.name).pdns_do()
+        except PDNSException as e:
+            # Domain already exists
+            if e.response.status_code == 409:
+                pass
+            else:
+                raise e
+        else:
+            created = True
+            PDNSChangeTracker.PDNSChange(domain_name=domain.name).update_catalog()
+
+        # modifications actually merged with additions in CreateUpdateDeleteRRSets
+        modifications = {(rrset.type, rrset.subname) for rrset in domain.rrset_set.all()}
+        deletions = {(rrset['type'], rrset['subname']) for rrset in pdns.get_rrset_datas(domain)} - modifications
+        deletions.discard(('SOA', ''))  # do not remove SOA record
+
+        # Update zone on nslord, propagate to nsmaster
+        PDNSChangeTracker.CreateUpdateDeleteRRSets(domain.name, set(), modifications, deletions).pdns_do()
+        pdns._pdns_put(pdns.NSMASTER, '/zones/{}/axfr-retrieve'.format(pdns.pdns_id(domain.name)))
+
+        return created