sync-to-pdns.py 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. from django.core.management import BaseCommand, CommandError, call_command
  2. from django.db import transaction
  3. from desecapi import pdns
  4. from desecapi.exceptions import PDNSException
  5. from desecapi.models import Domain
  6. from desecapi.pdns_change_tracker import PDNSChangeTracker
  7. class Command(BaseCommand):
  8. help = 'Sync RRsets from local API database to pdns.'
  9. def add_arguments(self, parser):
  10. parser.add_argument('domain-name', nargs='*',
  11. help='Domain name to sync. If omitted, will import all API domains.')
  12. def handle(self, *args, **options):
  13. domains = Domain.objects.all()
  14. if options['domain-name']:
  15. domains = domains.filter(name__in=options['domain-name'])
  16. domain_names = domains.values_list('name', flat=True)
  17. for domain_name in options['domain-name']:
  18. if domain_name not in domain_names:
  19. raise CommandError('{} is not a known domain'.format(domain_name))
  20. catalog_alignment = False
  21. for domain in domains:
  22. self.stdout.write('%s ...' % domain.name, ending='')
  23. try:
  24. created = self._sync_domain(domain)
  25. if created:
  26. self.stdout.write(f' created (was missing) ...', ending='')
  27. catalog_alignment = True
  28. self.stdout.write(' synced')
  29. except Exception as e:
  30. self.stdout.write(' failed')
  31. msg = 'Error while processing {}: {}'.format(domain.name, e)
  32. raise CommandError(msg)
  33. if catalog_alignment:
  34. call_command('align-catalog-zone')
  35. @staticmethod
  36. @transaction.atomic
  37. def _sync_domain(domain):
  38. created = False
  39. # Create domain on pdns if it does not exist
  40. try:
  41. PDNSChangeTracker.CreateDomain(domain_name=domain.name).pdns_do()
  42. except PDNSException as e:
  43. # Domain already exists
  44. if e.response.status_code == 409:
  45. pass
  46. else:
  47. raise e
  48. else:
  49. created = True
  50. # modifications actually merged with additions in CreateUpdateDeleteRRSets
  51. modifications = {(rrset.type, rrset.subname) for rrset in domain.rrset_set.all()}
  52. deletions = {(rrset['type'], rrset['subname']) for rrset in pdns.get_rrset_datas(domain)} - modifications
  53. deletions.discard(('SOA', '')) # do not remove SOA record
  54. # Update zone on nslord, propagate to nsmaster
  55. PDNSChangeTracker.CreateUpdateDeleteRRSets(domain.name, set(), modifications, deletions).pdns_do()
  56. pdns._pdns_put(pdns.NSMASTER, '/zones/{}/axfr-retrieve'.format(pdns.pdns_id(domain.name)))
  57. return created