Skip to content

Commit e9c8a74

Browse files
committed
Add DNS record helpers, service templates, and zone diff tools
Introduces record type-specific helper methods (A, AAAA, CNAME, MX, TXT), service templates for Google Workspace and Microsoft 365, and utilities for exporting/importing DNS records in multiple formats. Adds diff and compare functionality for DNS zones, with async support for all new features. Updates documentation to reflect new usage patterns and examples.
1 parent 08d099d commit e9c8a74

5 files changed

Lines changed: 1530 additions & 3 deletions

File tree

docs/api/lzl/cloudflare.md

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,187 @@ for zone_result in result.results:
457457
show_root_heading: true
458458
show_source: false
459459

460+
## Record Type-Specific Helpers
461+
462+
Convenience methods for common record types that accept zone name or ID:
463+
464+
```python
465+
# A record (IPv4)
466+
client.dns.add_a_record("example.com", "www", "192.0.2.1", proxied=True)
467+
468+
# AAAA record (IPv6)
469+
client.dns.add_aaaa_record("example.com", "@", "2001:db8::1")
470+
471+
# CNAME record
472+
client.dns.add_cname_record("example.com", "blog", "blog.example.net", proxied=True)
473+
474+
# MX record
475+
client.dns.add_mx_record("example.com", "@", "mail.example.com", priority=10)
476+
477+
# TXT record
478+
client.dns.add_txt_record("example.com", "@", "v=spf1 include:_spf.google.com ~all")
479+
```
480+
481+
## Service Templates
482+
483+
Pre-configured templates for common email providers:
484+
485+
### Google Workspace
486+
487+
```python
488+
# Add all Google Workspace MX records
489+
records = client.dns.add_google_workspace_mx("example.com")
490+
491+
# Add SPF for Google
492+
client.dns.add_spf_record("example.com", providers=["google"])
493+
494+
# Add DMARC
495+
client.dns.add_dmarc_record(
496+
"example.com",
497+
policy="quarantine",
498+
rua="dmarc-reports@example.com",
499+
)
500+
```
501+
502+
### Microsoft 365
503+
504+
```python
505+
# Add Microsoft 365 MX record
506+
records = client.dns.add_microsoft_365_mx("example.com")
507+
508+
# Add SPF for Microsoft
509+
client.dns.add_spf_record("example.com", providers=["microsoft"])
510+
```
511+
512+
### Combined Setup
513+
514+
```python
515+
# SPF for both Google and Microsoft
516+
client.dns.add_spf_record(
517+
"example.com",
518+
providers=["google", "microsoft"],
519+
ip4=["203.0.113.1"], # Additional IPs
520+
policy="-all", # Strict policy
521+
)
522+
523+
# Full DMARC setup
524+
client.dns.add_dmarc_record(
525+
"example.com",
526+
policy="reject",
527+
rua="dmarc-aggregate@example.com",
528+
ruf="dmarc-forensic@example.com",
529+
pct=100,
530+
)
531+
```
532+
533+
## Export / Import
534+
535+
### Exporting Records
536+
537+
```python
538+
# Export to JSON
539+
json_data = client.dns.export_records("example.com", format="json")
540+
with open("dns_backup.json", "w") as f:
541+
f.write(json_data)
542+
543+
# Export to BIND zone file format
544+
bind_data = client.dns.export_records("example.com", format="bind")
545+
546+
# Export as Python dict
547+
records = client.dns.export_records("example.com", format="dict")
548+
549+
# Filter by record types
550+
mx_records = client.dns.export_records(
551+
"example.com",
552+
format="json",
553+
record_types=["MX", "TXT"],
554+
)
555+
556+
# Exclude certain types
557+
filtered = client.dns.export_records(
558+
"example.com",
559+
format="json",
560+
exclude_types=["NS", "SOA"],
561+
)
562+
```
563+
564+
### Importing Records
565+
566+
```python
567+
# Import from JSON file
568+
with open("dns_backup.json") as f:
569+
json_data = f.read()
570+
records = client.dns.import_records("example.com", json_data, format="json")
571+
572+
# Import from dict (merge mode - upserts records)
573+
records_data = [
574+
{"name": "www.example.com", "type": "A", "content": "192.0.2.1"},
575+
{"name": "api.example.com", "type": "A", "content": "192.0.2.2"},
576+
]
577+
records = client.dns.import_records("example.com", records_data, format="dict", merge=True)
578+
579+
# Import without merging (only creates new records)
580+
records = client.dns.import_records("example.com", records_data, format="dict", merge=False)
581+
```
582+
583+
## Diff / Preview
584+
585+
Preview changes before applying:
586+
587+
```python
588+
# Preview changes without applying
589+
desired_records = [
590+
{"dns_name": "www", "record_type": "A", "targets": ["192.0.2.1"]},
591+
{"dns_name": "api", "record_type": "A", "targets": ["192.0.2.2"]},
592+
]
593+
594+
diff = client.diff_dns_records(desired_records, root_domain="example.com")
595+
596+
for zone_result in diff.results:
597+
print(f"Zone: {zone_result.zone_name}")
598+
599+
print("To Create:")
600+
for change in zone_result.to_create:
601+
print(f" + {change.name} {change.type} -> {change.content}")
602+
603+
print("To Update:")
604+
for change in zone_result.to_update:
605+
print(f" ~ {change.name} {change.type} -> {change.content}")
606+
607+
print("To Delete:")
608+
for change in zone_result.to_delete:
609+
print(f" - {change.name} {change.type} -> {change.content}")
610+
```
611+
612+
### Comparing Zones
613+
614+
Compare DNS records between two zones:
615+
616+
```python
617+
comparison = client.compare_zones("example.com", "example.net")
618+
619+
print(f"Records only in {comparison['source_zone']}:")
620+
for rec in comparison["only_in_source"]:
621+
print(f" {rec['name']} {rec['type']} {rec['content']}")
622+
623+
print(f"Records only in {comparison['target_zone']}:")
624+
for rec in comparison["only_in_target"]:
625+
print(f" {rec['name']} {rec['type']} {rec['content']}")
626+
627+
print("Different content:")
628+
for rec in comparison["different"]:
629+
print(f" {rec['name']} {rec['type']}")
630+
print(f" Source: {rec['source_content']}")
631+
print(f" Target: {rec['target_content']}")
632+
633+
# Compare specific record types only
634+
comparison = client.compare_zones(
635+
"example.com",
636+
"example.net",
637+
record_types=["A", "CNAME"],
638+
)
639+
```
640+
460641
## Async Support
461642

462643
All methods have async counterparts with the `a` prefix:
@@ -479,6 +660,22 @@ records = await client.dns.aupsert_many(zone_id, [...])
479660

480661
# Apply
481662
result = await client.aapply_dns_records(records, root_domain="example.com")
663+
664+
# Record type helpers
665+
await client.dns.aadd_a_record("example.com", "www", "192.0.2.1")
666+
await client.dns.aadd_mx_record("example.com", "@", "mail.example.com", 10)
667+
668+
# Service templates
669+
await client.dns.aadd_google_workspace_mx("example.com")
670+
await client.dns.aadd_spf_record("example.com", providers=["google"])
671+
672+
# Export/Import
673+
json_data = await client.dns.aexport_records("example.com")
674+
await client.dns.aimport_records("example.com", json_data)
675+
676+
# Diff/Compare
677+
diff = await client.adiff_dns_records(records, root_domain="example.com")
678+
comparison = await client.acompare_zones("example.com", "example.net")
482679
```
483680

484681
## Context Manager

src/lzl/api/cloudflare/README.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,68 @@ print("To update:", result.results[0].to_update)
201201
print("To delete:", result.results[0].to_delete)
202202
```
203203

204+
## Record Type Helpers
205+
206+
Convenience methods for common record types that accept zone name or ID:
207+
208+
```python
209+
# Add records using zone name directly (no zone_id lookup needed)
210+
client.dns.add_a_record("example.com", "www", "192.0.2.1", proxied=True)
211+
client.dns.add_aaaa_record("example.com", "@", "2001:db8::1")
212+
client.dns.add_cname_record("example.com", "blog", "blog.example.net")
213+
client.dns.add_mx_record("example.com", "@", "mail.example.com", priority=10)
214+
client.dns.add_txt_record("example.com", "@", "v=spf1 include:_spf.google.com ~all")
215+
```
216+
217+
## Service Templates
218+
219+
Pre-configured templates for common email providers:
220+
221+
```python
222+
# Google Workspace MX records
223+
client.dns.add_google_workspace_mx("example.com")
224+
225+
# Microsoft 365 MX record
226+
client.dns.add_microsoft_365_mx("example.com")
227+
228+
# SPF record with providers
229+
client.dns.add_spf_record("example.com", providers=["google", "microsoft"])
230+
231+
# DMARC record
232+
client.dns.add_dmarc_record(
233+
"example.com",
234+
policy="quarantine",
235+
rua="dmarc-reports@example.com",
236+
)
237+
```
238+
239+
## Export / Import
240+
241+
```python
242+
# Export to JSON
243+
json_data = client.dns.export_records("example.com", format="json")
244+
245+
# Export to BIND format
246+
bind_data = client.dns.export_records("example.com", format="bind")
247+
248+
# Import records (with merge)
249+
records = client.dns.import_records("example.com", json_data, merge=True)
250+
```
251+
252+
## Diff / Preview
253+
254+
```python
255+
# Preview changes without applying
256+
diff = client.diff_dns_records(records, root_domain="example.com")
257+
for zone_result in diff.results:
258+
print(f"Create: {len(zone_result.to_create)}")
259+
print(f"Update: {len(zone_result.to_update)}")
260+
print(f"Delete: {len(zone_result.to_delete)}")
261+
262+
# Compare two zones
263+
comparison = client.compare_zones("example.com", "example.net")
264+
```
265+
204266
## Async Support
205267

206268
All methods have async counterparts with the `a` prefix:

src/lzl/api/cloudflare/__init__.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@
4242
RecordChange,
4343
)
4444
from .resources import DNSResource
45+
from .resources.dns import (
46+
GOOGLE_WORKSPACE_MX,
47+
SPF_GOOGLE,
48+
SPF_MICROSOFT,
49+
SPF_GOOGLE_AND_MICROSOFT,
50+
)
4551

4652
__all__ = [
4753
# Settings
@@ -62,6 +68,11 @@
6268
"RecordChange",
6369
# Resources
6470
"DNSResource",
71+
# Constants
72+
"GOOGLE_WORKSPACE_MX",
73+
"SPF_GOOGLE",
74+
"SPF_MICROSOFT",
75+
"SPF_GOOGLE_AND_MICROSOFT",
6576
# Singletons
6677
"settings",
6778
"client",

0 commit comments

Comments
 (0)