feat(defzone): Add plist argument format for SOA records

These records have so many fields that it's difficult to track what's
what in a long list. For convenience they're now specified in plist
format (see the example).

There isn't really a point to this because the SOA record is the one I
care the *least* about practically as Cloud DNS sets it for me, but
whatever.
This commit is contained in:
Vincent Ambo 2019-12-22 03:07:59 +00:00
parent 8c86b9b5f6
commit 5cc37a15a5
2 changed files with 45 additions and 27 deletions

View file

@ -8,38 +8,50 @@
"Evaluate a record definition and turn it into a zone file "Evaluate a record definition and turn it into a zone file
record in ZONE, optionally prefixed with SUBDOMAIN." record in ZONE, optionally prefixed with SUBDOMAIN."
(declare (indent defun)) ; TODO(tazjin): remove (cl-labels ((plist->alist (plist)
(let ((name (if subdomain (s-join "." (list subdomain zone)) zone))) (when plist
(pcase record (cons
(`(SOA . (,ttl . (,mname ,rname ,serial ,refresh ,retry ,expire ,min))) (cons (car plist) (cadr plist))
(format "%s %s IN SOA %s %s %s %s %s %s %s" (plist->alist (cddr plist))))))
name ttl mname rname serial refresh retry expire min)) (let ((name (if subdomain (s-join "." (list subdomain zone)) zone)))
(pcase record
;; SOA RDATA (RFC 1035; 3.3.13)
((and `(SOA . (,ttl . ,keys))
(let (map (:mname mname) (:rname rname) (:serial serial)
(:refresh refresh) (:retry retry) (:expire expire)
(:minimum min))
(plist->alist keys)))
(if-let ((missing (-filter #'null (not (list mname rname serial
refresh retry expire min)))))
(error "Missing fields in SOA record: %s" missing)
(format "%s %s IN SOA %s %s %s %s %s %s %s"
name ttl mname rname serial refresh retry expire min)))
(`(NS . (,ttl . ,targets)) (`(NS . (,ttl . ,targets))
(->> targets (->> targets
(-map (lambda (target) (format "%s %s IN NS %s" name ttl target))) (-map (lambda (target) (format "%s %s IN NS %s" name ttl target)))
(s-join "\n"))) (s-join "\n")))
(`(MX . (,ttl . ,pairs)) (`(MX . (,ttl . ,pairs))
(->> pairs (->> pairs
(-map (-lambda ((preference . exchange)) (-map (-lambda ((preference . exchange))
(format "%s %s IN MX %s %s" name ttl preference exchange))) (format "%s %s IN MX %s %s" name ttl preference exchange)))
(s-join "\n"))) (s-join "\n")))
(`(TXT ,ttl ,text) (format "%s %s IN TXT %s" name ttl (prin1-to-string text))) (`(TXT ,ttl ,text) (format "%s %s IN TXT %s" name ttl (prin1-to-string text)))
(`(A . (,ttl . ,ips)) (`(A . (,ttl . ,ips))
(->> ips (->> ips
(-map (lambda (ip) (format "%s %s IN A %s" name ttl ip))) (-map (lambda (ip) (format "%s %s IN A %s" name ttl ip)))
(s-join "\n"))) (s-join "\n")))
(`(CNAME ,ttl ,target) (format "%s %s IN CNAME %s" name ttl target)) (`(CNAME ,ttl ,target) (format "%s %s IN CNAME %s" name ttl target))
((and `(,sub . ,records) ((and `(,sub . ,records)
(guard (stringp sub))) (guard (stringp sub)))
(s-join "\n" (-map (lambda (r) (record-to-record zone r sub)) records))) (s-join "\n" (-map (lambda (r) (record-to-record zone r sub)) records)))
(_ (error "Invalid record definition: %s" record))))) (_ (error "Invalid record definition: %s" record))))))
(defmacro defzone (fqdn &rest records) (defmacro defzone (fqdn &rest records)
"Generate zone file for the zone at FQDN from a simple DSL." "Generate zone file for the zone at FQDN from a simple DSL."

View file

@ -1,8 +1,14 @@
;;; example.el - usage example for defzone macro ;;; example.el - usage example for defzone macro
(defzone "tazj.in." (defzone "tazj.in."
(SOA 21600 "ns-cloud-a1.googledomains.com." "cloud-dns-hostmaster.google.com." (SOA 21600
123 21600 3600 1209600 300) :mname "ns-cloud-a1.googledomains.com."
:rname "cloud-dns-hostmaster.google.com."
:serial 123
:refresh 21600
:retry 3600
:expire 1209600
:minimum 300)
(NS 21600 (NS 21600
"ns-cloud-a1.googledomains.com." "ns-cloud-a1.googledomains.com."