Adds get_extra_dns_servers(), add_extra_dns_server(), and the
high-level ensure_extra_dns_server() which registers a node and
enforces dns=yes + domain_check=yes in a single call. Also adds
the generic post() helper. 10 new tests, 141 total.
- Fix dnspython silently relativizing in-zone FQDN targets to '@' by
calling rdata.to_text(origin=origin, relativize=False); CoreDNS MySQL
requires absolute FQDNs in RDATA and was serving '.' for any CNAME/MX
pointing to the zone apex
- Reorder write_zone to delete stale records before inserting new ones
so a brief NXDOMAIN is preferred over briefly serving duplicate records
- Rework save-queue batch loop: keep batch open until queue is empty
rather than closing after a fixed timeout, so sequential DA zone pushes
accumulate into a single batch
- Add managed_by='directadmin' to _ensure_zone_exists for new and
legacy NULL rows
Move all outbound DirectAdmin HTTP logic out of ReconciliationWorker and
into a dedicated, independently testable DirectAdminClient class:
- directdnsonly/app/da/client.py: list_domains (paginated JSON + legacy
fallback), get (authenticated GET to any CMD_* endpoint), _login
(DA Evo session-cookie fallback), _parse_legacy_domain_list
- directdnsonly/app/da/__init__.py: public re-export of DirectAdminClient
- reconciler.py: now purely reconciliation logic; instantiates a client
per configured server — no HTTP code remaining
- tests/test_da_client.py: 16 dedicated tests for DirectAdminClient
- tests/test_reconciler.py: mocks at the DirectAdminClient class boundary
instead of the internal _fetch_da_domains method
Bumped to 2.2.0 — DirectAdminClient is now a first-class public API.