sync-to-pdns.py 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  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(
  11. "domain-name",
  12. nargs="*",
  13. help="Domain name to sync. If omitted, will import all API domains.",
  14. )
  15. def handle(self, *args, **options):
  16. domains = Domain.objects.all()
  17. if options["domain-name"]:
  18. domains = domains.filter(name__in=options["domain-name"])
  19. domain_names = domains.values_list("name", flat=True)
  20. for domain_name in options["domain-name"]:
  21. if domain_name not in domain_names:
  22. raise CommandError("{} is not a known domain".format(domain_name))
  23. catalog_alignment = False
  24. for domain in domains:
  25. self.stdout.write("%s ..." % domain.name, ending="")
  26. try:
  27. created = self._sync_domain(domain)
  28. if created:
  29. self.stdout.write(f" created (was missing) ...", ending="")
  30. catalog_alignment = True
  31. self.stdout.write(" synced")
  32. except Exception as e:
  33. self.stdout.write(" failed")
  34. msg = "Error while processing {}: {}".format(domain.name, e)
  35. raise CommandError(msg)
  36. if catalog_alignment:
  37. call_command("align-catalog-zone")
  38. @staticmethod
  39. @transaction.atomic
  40. def _sync_domain(domain):
  41. created = False
  42. # Create domain on pdns if it does not exist
  43. try:
  44. PDNSChangeTracker.CreateDomain(domain_name=domain.name).pdns_do()
  45. except PDNSException as e:
  46. # Domain already exists
  47. if e.response.status_code == 409:
  48. pass
  49. else:
  50. raise e
  51. else:
  52. created = True
  53. # modifications actually merged with additions in CreateUpdateDeleteRRSets
  54. modifications = {
  55. (rrset.type, rrset.subname) for rrset in domain.rrset_set.all()
  56. }
  57. deletions = {
  58. (rrset["type"], rrset["subname"]) for rrset in pdns.get_rrset_datas(domain)
  59. } - modifications
  60. deletions.discard(("SOA", "")) # do not remove SOA record
  61. # Update zone on nslord, propagate to nsmaster
  62. PDNSChangeTracker.CreateUpdateDeleteRRSets(
  63. domain.name, set(), modifications, deletions
  64. ).pdns_do()
  65. pdns._pdns_put(
  66. pdns.NSMASTER, "/zones/{}/axfr-retrieve".format(pdns.pdns_id(domain.name))
  67. )
  68. return created