0
# DNS Management
1
2
The DNS service provides a unified interface for DNS zone and record management across 15+ DNS providers including Route 53, CloudFlare, Google DNS, Rackspace DNS, and many others.
3
4
## Providers
5
6
```python { .api }
7
from libcloud.dns.types import Provider
8
9
class Provider:
10
"""Enumeration of supported DNS providers"""
11
ROUTE53 = 'route53'
12
RACKSPACE = 'rackspace'
13
ZERIGO = 'zerigo'
14
DNSIMPLE = 'dnsimple'
15
LINODE = 'linode'
16
DNSPARK = 'dnspark'
17
CLOUDFLARE = 'cloudflare'
18
GOOGLE = 'google'
19
GANDIAPI = 'gandiapi'
20
POINTDNS = 'pointdns'
21
RCODEZERO = 'rcodezero'
22
NFSN = 'nfsn'
23
NSONE = 'nsone'
24
LIQUIDWEB = 'liquidweb'
25
# ... more providers
26
```
27
28
## Driver Factory
29
30
```python { .api }
31
from libcloud.dns.providers import get_driver
32
33
def get_driver(provider: Provider) -> type[DNSDriver]
34
```
35
36
Get the driver class for a specific DNS provider.
37
38
**Parameters:**
39
- `provider`: Provider identifier from the Provider enum
40
41
**Returns:**
42
- Driver class for the specified provider
43
44
**Example:**
45
```python
46
from libcloud.dns.types import Provider
47
from libcloud.dns.providers import get_driver
48
49
# Get Route 53 driver class
50
cls = get_driver(Provider.ROUTE53)
51
52
# Initialize driver with credentials
53
driver = cls('access_key', 'secret_key')
54
```
55
56
## Core Classes
57
58
### DNSDriver
59
60
```python { .api }
61
class DNSDriver(BaseDriver):
62
"""Base class for all DNS drivers"""
63
64
def list_zones(self) -> List[Zone]
65
def get_zone(self, zone_id: str) -> Zone
66
def create_zone(self, domain: str, type: str = 'master', ttl: int = None, extra: Dict = None) -> Zone
67
def update_zone(self, zone: Zone, domain: str = None, type: str = None, ttl: int = None, extra: Dict = None) -> Zone
68
def delete_zone(self, zone: Zone) -> bool
69
def list_records(self, zone: Zone, type: RecordType = None) -> List[Record]
70
def get_record(self, zone_id: str, record_id: str) -> Record
71
def create_record(self, name: str, zone: Zone, type: RecordType, data: str, extra: Dict = None) -> Record
72
def update_record(self, record: Record, name: str = None, type: RecordType = None, data: str = None, extra: Dict = None) -> Record
73
def delete_record(self, record: Record) -> bool
74
def ex_create_multi_value_record(self, name: str, zone: Zone, type: RecordType, data: List[str], extra: Dict = None) -> List[Record]
75
```
76
77
Base class that all DNS drivers inherit from. Provides methods for managing DNS zones and records.
78
79
**Key Methods:**
80
81
- `list_zones()`: List all DNS zones in the account
82
- `create_zone()`: Create a new DNS zone
83
- `list_records()`: List DNS records in a zone
84
- `create_record()`: Create a new DNS record
85
- `update_record()`: Update an existing DNS record
86
- `delete_record()`: Delete a DNS record
87
88
### Zone
89
90
```python { .api }
91
class Zone:
92
"""Represents a DNS zone/domain"""
93
94
id: str
95
domain: str
96
type: str
97
ttl: int
98
driver: DNSDriver
99
extra: Dict[str, Any]
100
101
def list_records(self, type: RecordType = None) -> List[Record]
102
def create_record(self, name: str, type: RecordType, data: str, extra: Dict = None) -> Record
103
def update_record(self, record: Record, name: str = None, type: RecordType = None, data: str = None, extra: Dict = None) -> Record
104
def delete_record(self, record: Record) -> bool
105
def delete(self) -> bool
106
def update(self, domain: str = None, type: str = None, ttl: int = None, extra: Dict = None) -> Zone
107
```
108
109
Represents a DNS zone (domain) that contains DNS records.
110
111
**Properties:**
112
- `id`: Unique zone identifier
113
- `domain`: Domain name (e.g., "example.com")
114
- `type`: Zone type ("master", "slave", etc.)
115
- `ttl`: Time-to-live in seconds
116
- `extra`: Provider-specific metadata
117
118
**Methods:**
119
- `list_records()`: List records in this zone
120
- `create_record()`: Create a record in this zone
121
- `delete()`: Delete the entire zone
122
123
### Record
124
125
```python { .api }
126
class Record:
127
"""Represents a DNS record"""
128
129
id: str
130
name: str
131
type: RecordType
132
data: str
133
zone: Zone
134
driver: DNSDriver
135
ttl: int
136
extra: Dict[str, Any]
137
138
def update(self, name: str = None, type: RecordType = None, data: str = None, extra: Dict = None) -> Record
139
def delete(self) -> bool
140
```
141
142
Represents a DNS record within a zone.
143
144
**Properties:**
145
- `id`: Unique record identifier
146
- `name`: Record name (subdomain or "@" for root)
147
- `type`: Record type (A, AAAA, CNAME, etc.)
148
- `data`: Record data/value
149
- `zone`: Parent zone
150
- `ttl`: Time-to-live in seconds
151
- `extra`: Provider-specific metadata
152
153
**Methods:**
154
- `update()`: Update this record
155
- `delete()`: Delete this record
156
157
### RecordType
158
159
```python { .api }
160
class RecordType:
161
"""DNS record types enumeration"""
162
A = 'A'
163
AAAA = 'AAAA'
164
CNAME = 'CNAME'
165
MX = 'MX'
166
NS = 'NS'
167
PTR = 'PTR'
168
SOA = 'SOA'
169
SRV = 'SRV'
170
TXT = 'TXT'
171
SPF = 'SPF'
172
CAA = 'CAA'
173
DNAME = 'DNAME'
174
NAPTR = 'NAPTR'
175
HINFO = 'HINFO'
176
SSHFP = 'SSHFP'
177
TLSA = 'TLSA'
178
```
179
180
Enumeration of supported DNS record types.
181
182
## Usage Examples
183
184
### Basic Zone Management
185
186
```python
187
from libcloud.dns.types import Provider, RecordType
188
from libcloud.dns.providers import get_driver
189
190
# Initialize driver
191
cls = get_driver(Provider.ROUTE53)
192
driver = cls('access_key', 'secret_key')
193
194
# List existing zones
195
zones = driver.list_zones()
196
for zone in zones:
197
print(f"Zone: {zone.domain} (Type: {zone.type}, TTL: {zone.ttl})")
198
199
# Create a new zone
200
zone = driver.create_zone(
201
domain='example.com',
202
type='master',
203
ttl=3600
204
)
205
print(f"Created zone: {zone.domain} ({zone.id})")
206
207
# Get a specific zone
208
try:
209
zone = driver.get_zone('Z123456789')
210
print(f"Found zone: {zone.domain}")
211
except Exception as e:
212
print(f"Zone not found: {e}")
213
```
214
215
### DNS Record Management
216
217
```python
218
# List all records in a zone
219
records = driver.list_records(zone)
220
print(f"Zone {zone.domain} has {len(records)} records")
221
222
for record in records:
223
print(f" {record.name} {record.type} {record.data} (TTL: {record.ttl})")
224
225
# Create different types of records
226
# A record
227
a_record = driver.create_record(
228
name='www',
229
zone=zone,
230
type=RecordType.A,
231
data='192.168.1.100'
232
)
233
print(f"Created A record: {a_record.name}.{zone.domain} -> {a_record.data}")
234
235
# CNAME record
236
cname_record = driver.create_record(
237
name='blog',
238
zone=zone,
239
type=RecordType.CNAME,
240
data='www.example.com.'
241
)
242
print(f"Created CNAME record: {cname_record.name}.{zone.domain} -> {cname_record.data}")
243
244
# MX record
245
mx_record = driver.create_record(
246
name='@', # Root domain
247
zone=zone,
248
type=RecordType.MX,
249
data='10 mail.example.com.',
250
extra={'priority': 10} # Some providers store priority in extra
251
)
252
print(f"Created MX record with priority 10")
253
254
# TXT record
255
txt_record = driver.create_record(
256
name='@',
257
zone=zone,
258
type=RecordType.TXT,
259
data='v=spf1 include:_spf.google.com ~all'
260
)
261
print(f"Created TXT record for SPF")
262
```
263
264
### Record Operations Using Zone Methods
265
266
```python
267
# Alternative way using zone methods
268
zone = driver.get_zone('Z123456789')
269
270
# Create record using zone method
271
record = zone.create_record(
272
name='api',
273
type=RecordType.A,
274
data='10.0.1.50',
275
extra={'ttl': 300} # 5 minute TTL
276
)
277
278
# List records in zone using zone method
279
records = zone.list_records(type=RecordType.A)
280
print(f"Found {len(records)} A records")
281
282
# Update a record
283
updated_record = record.update(
284
data='10.0.1.51', # New IP address
285
extra={'ttl': 600} # New TTL
286
)
287
print(f"Updated record: {updated_record.data}")
288
289
# Delete a record
290
success = record.delete()
291
print(f"Record deleted: {success}")
292
```
293
294
### Advanced Record Management
295
296
```python
297
# Create multiple A records for load balancing (if supported)
298
try:
299
records = driver.ex_create_multi_value_record(
300
name='lb',
301
zone=zone,
302
type=RecordType.A,
303
data=['192.168.1.10', '192.168.1.11', '192.168.1.12']
304
)
305
print(f"Created {len(records)} load balancer records")
306
except AttributeError:
307
# Fallback: create individual records
308
ips = ['192.168.1.10', '192.168.1.11', '192.168.1.12']
309
for i, ip in enumerate(ips):
310
record = driver.create_record(
311
name='lb',
312
zone=zone,
313
type=RecordType.A,
314
data=ip
315
)
316
print(f"Created LB record {i+1}: {record.data}")
317
318
# Create SRV record for services
319
srv_record = driver.create_record(
320
name='_http._tcp',
321
zone=zone,
322
type=RecordType.SRV,
323
data='10 5 80 web.example.com.',
324
extra={'priority': 10, 'weight': 5, 'port': 80}
325
)
326
print(f"Created SRV record for HTTP service")
327
328
# Create CAA record for certificate authority authorization
329
caa_record = driver.create_record(
330
name='@',
331
zone=zone,
332
type=RecordType.CAA,
333
data='0 issue "letsencrypt.org"'
334
)
335
print(f"Created CAA record for Let's Encrypt")
336
```
337
338
### Bulk Operations and Management
339
340
```python
341
def setup_basic_dns(driver, domain, config):
342
"""Setup basic DNS records for a domain"""
343
344
# Create or get zone
345
try:
346
zone = None
347
for z in driver.list_zones():
348
if z.domain == domain:
349
zone = z
350
break
351
352
if not zone:
353
zone = driver.create_zone(domain, ttl=3600)
354
print(f"Created zone: {domain}")
355
except Exception as e:
356
print(f"Error creating zone: {e}")
357
return
358
359
# Create basic records
360
records_to_create = [
361
# A records
362
('www', RecordType.A, config['web_ip']),
363
('mail', RecordType.A, config['mail_ip']),
364
('ftp', RecordType.A, config['ftp_ip']),
365
366
# CNAME records
367
('blog', RecordType.CNAME, f'www.{domain}.'),
368
('shop', RecordType.CNAME, f'www.{domain}.'),
369
370
# MX record
371
('@', RecordType.MX, f"10 mail.{domain}."),
372
373
# TXT records
374
('@', RecordType.TXT, config['spf_record']),
375
('_dmarc', RecordType.TXT, config['dmarc_record']),
376
]
377
378
created_records = []
379
for name, record_type, data in records_to_create:
380
try:
381
record = driver.create_record(
382
name=name,
383
zone=zone,
384
type=record_type,
385
data=data
386
)
387
created_records.append(record)
388
print(f"Created: {name} {record_type} {data}")
389
except Exception as e:
390
print(f"Failed to create {name} {record_type}: {e}")
391
392
return zone, created_records
393
394
# Usage
395
dns_config = {
396
'web_ip': '192.168.1.100',
397
'mail_ip': '192.168.1.101',
398
'ftp_ip': '192.168.1.102',
399
'spf_record': 'v=spf1 include:_spf.google.com ~all',
400
'dmarc_record': 'v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com'
401
}
402
403
zone, records = setup_basic_dns(driver, 'example.com', dns_config)
404
```
405
406
### Multi-Provider DNS Management
407
408
```python
409
from libcloud.dns.types import Provider
410
from libcloud.dns.providers import get_driver
411
412
# Configure multiple DNS providers
413
dns_providers = {
414
'route53': {
415
'driver': get_driver(Provider.ROUTE53),
416
'credentials': ('aws_access_key', 'aws_secret_key')
417
},
418
'cloudflare': {
419
'driver': get_driver(Provider.CLOUDFLARE),
420
'credentials': ('email', 'api_key')
421
}
422
}
423
424
# Initialize drivers
425
drivers = {}
426
for name, config in dns_providers.items():
427
cls = config['driver']
428
drivers[name] = cls(*config['credentials'])
429
430
# Compare DNS records across providers
431
def compare_dns_records(domain):
432
"""Compare DNS records for a domain across providers"""
433
provider_records = {}
434
435
for provider_name, driver in drivers.items():
436
try:
437
zones = driver.list_zones()
438
zone = None
439
for z in zones:
440
if z.domain == domain:
441
zone = z
442
break
443
444
if zone:
445
records = driver.list_records(zone)
446
provider_records[provider_name] = {
447
(r.name, r.type): r.data for r in records
448
}
449
print(f"{provider_name}: {len(records)} records")
450
else:
451
print(f"{provider_name}: Domain not found")
452
provider_records[provider_name] = {}
453
except Exception as e:
454
print(f"Error accessing {provider_name}: {e}")
455
provider_records[provider_name] = {}
456
457
# Find differences
458
all_record_keys = set()
459
for records in provider_records.values():
460
all_record_keys.update(records.keys())
461
462
print(f"\nRecord comparison for {domain}:")
463
for key in sorted(all_record_keys):
464
name, record_type = key
465
values = []
466
for provider in provider_records:
467
value = provider_records[provider].get(key, 'MISSING')
468
values.append(f"{provider}: {value}")
469
print(f" {name} {record_type}: {', '.join(values)}")
470
471
# Usage
472
compare_dns_records('example.com')
473
```
474
475
### DNS Record Monitoring and Health Checks
476
477
```python
478
import socket
479
import time
480
from typing import Dict, List
481
482
def verify_dns_propagation(domain: str, record_name: str, record_type: str, expected_value: str, nameservers: List[str] = None) -> Dict[str, str]:
483
"""Verify DNS record propagation across nameservers"""
484
import dns.resolver
485
486
if not nameservers:
487
nameservers = ['8.8.8.8', '1.1.1.1', '208.67.222.222'] # Google, Cloudflare, OpenDNS
488
489
results = {}
490
fqdn = f"{record_name}.{domain}" if record_name != '@' else domain
491
492
for ns in nameservers:
493
try:
494
resolver = dns.resolver.Resolver()
495
resolver.nameservers = [ns]
496
497
answers = resolver.query(fqdn, record_type)
498
actual_value = str(answers[0])
499
500
results[ns] = {
501
'value': actual_value,
502
'matches': actual_value == expected_value,
503
'status': 'OK' if actual_value == expected_value else 'MISMATCH'
504
}
505
except Exception as e:
506
results[ns] = {
507
'value': None,
508
'matches': False,
509
'status': f'ERROR: {e}'
510
}
511
512
return results
513
514
def monitor_dns_changes(driver, zone, record_name: str, check_interval: int = 60):
515
"""Monitor DNS record changes"""
516
print(f"Monitoring DNS changes for {record_name}.{zone.domain}")
517
518
last_values = {}
519
520
while True:
521
try:
522
records = driver.list_records(zone)
523
current_values = {}
524
525
for record in records:
526
if record.name == record_name:
527
key = f"{record.name}_{record.type}"
528
current_values[key] = record.data
529
530
# Check for changes
531
for key, value in current_values.items():
532
if key in last_values and last_values[key] != value:
533
print(f"CHANGE DETECTED: {key} changed from {last_values[key]} to {value}")
534
elif key not in last_values:
535
print(f"NEW RECORD: {key} = {value}")
536
537
# Check for deletions
538
for key in last_values:
539
if key not in current_values:
540
print(f"RECORD DELETED: {key} was {last_values[key]}")
541
542
last_values = current_values.copy()
543
544
except Exception as e:
545
print(f"Error monitoring DNS: {e}")
546
547
time.sleep(check_interval)
548
549
# Usage
550
zone = driver.get_zone('Z123456789')
551
552
# Verify propagation after creating a record
553
record = driver.create_record('test', zone, RecordType.A, '192.168.1.50')
554
time.sleep(10) # Wait a bit for propagation
555
556
propagation_results = verify_dns_propagation('example.com', 'test', 'A', '192.168.1.50')
557
for ns, result in propagation_results.items():
558
print(f"Nameserver {ns}: {result['status']} ({result['value']})")
559
```
560
561
### Zone Backup and Restore
562
563
```python
564
import json
565
from datetime import datetime
566
567
def backup_zone(driver, zone) -> Dict:
568
"""Create a backup of all records in a zone"""
569
backup_data = {
570
'zone': {
571
'domain': zone.domain,
572
'type': zone.type,
573
'ttl': zone.ttl,
574
'extra': zone.extra
575
},
576
'records': [],
577
'backup_timestamp': datetime.now().isoformat()
578
}
579
580
records = driver.list_records(zone)
581
for record in records:
582
backup_data['records'].append({
583
'name': record.name,
584
'type': record.type,
585
'data': record.data,
586
'ttl': record.ttl,
587
'extra': record.extra
588
})
589
590
print(f"Backed up {len(records)} records from {zone.domain}")
591
return backup_data
592
593
def restore_zone(driver, backup_data: Dict, domain: str = None):
594
"""Restore a zone from backup data"""
595
domain = domain or backup_data['zone']['domain']
596
597
# Create zone if it doesn't exist
598
zone = None
599
try:
600
zones = driver.list_zones()
601
for z in zones:
602
if z.domain == domain:
603
zone = z
604
break
605
606
if not zone:
607
zone = driver.create_zone(
608
domain=domain,
609
type=backup_data['zone']['type'],
610
ttl=backup_data['zone']['ttl']
611
)
612
print(f"Created zone: {domain}")
613
except Exception as e:
614
print(f"Error creating zone: {e}")
615
return
616
617
# Restore records
618
restored_count = 0
619
for record_data in backup_data['records']:
620
try:
621
record = driver.create_record(
622
name=record_data['name'],
623
zone=zone,
624
type=record_data['type'],
625
data=record_data['data'],
626
extra=record_data.get('extra', {})
627
)
628
restored_count += 1
629
except Exception as e:
630
print(f"Failed to restore record {record_data['name']}: {e}")
631
632
print(f"Restored {restored_count} records to {domain}")
633
634
# Usage
635
zone = driver.get_zone('Z123456789')
636
637
# Create backup
638
backup = backup_zone(driver, zone)
639
640
# Save backup to file
641
with open(f'dns_backup_{zone.domain}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json', 'w') as f:
642
json.dump(backup, f, indent=2)
643
644
# Restore from backup (to same or different domain)
645
restore_zone(driver, backup, 'new-example.com')
646
```
647
648
## Exception Handling
649
650
```python { .api }
651
from libcloud.dns.types import (
652
ZoneError,
653
RecordError,
654
ZoneDoesNotExistError,
655
RecordDoesNotExistError,
656
ZoneAlreadyExistsError,
657
RecordAlreadyExistsError
658
)
659
660
class ZoneError(LibcloudError):
661
"""Base zone exception"""
662
663
class RecordError(LibcloudError):
664
"""Base record exception"""
665
666
class ZoneDoesNotExistError(ZoneError):
667
"""Zone does not exist"""
668
669
class RecordDoesNotExistError(RecordError):
670
"""Record does not exist"""
671
672
class ZoneAlreadyExistsError(ZoneError):
673
"""Zone already exists"""
674
675
class RecordAlreadyExistsError(RecordError):
676
"""Record already exists"""
677
```
678
679
**Error Handling Example:**
680
```python
681
from libcloud.dns.types import ZoneDoesNotExistError, RecordAlreadyExistsError
682
from libcloud.common.types import InvalidCredsError
683
684
try:
685
# Attempt to get a zone
686
zone = driver.get_zone('nonexistent-zone-id')
687
except ZoneDoesNotExistError:
688
print("Zone not found, creating new zone...")
689
zone = driver.create_zone('example.com')
690
691
try:
692
# Attempt to create a record
693
record = driver.create_record('www', zone, RecordType.A, '192.168.1.1')
694
except RecordAlreadyExistsError:
695
print("Record already exists, updating instead...")
696
existing_records = driver.list_records(zone, type=RecordType.A)
697
for record in existing_records:
698
if record.name == 'www':
699
record.update(data='192.168.1.1')
700
break
701
702
except InvalidCredsError:
703
print("Invalid DNS provider credentials")
704
```
705
706
## Provider-Specific Features
707
708
Different providers offer additional features through the `ex_*` parameter pattern:
709
710
```python
711
# Route 53 specific features
712
route53_driver = get_driver(Provider.ROUTE53)('access_key', 'secret_key')
713
714
# Create record with Route 53 specific options
715
record = route53_driver.create_record(
716
name='www',
717
zone=zone,
718
type=RecordType.A,
719
data='192.168.1.1',
720
extra={
721
'ttl': 300,
722
'health_check_id': 'hc-123456', # Health check integration
723
'set_identifier': 'primary', # For routing policies
724
'weight': 100 # Weighted routing
725
}
726
)
727
728
# CloudFlare specific features
729
cf_driver = get_driver(Provider.CLOUDFLARE)('email', 'api_key')
730
731
# Create record with CloudFlare proxy enabled
732
record = cf_driver.create_record(
733
name='www',
734
zone=zone,
735
type=RecordType.A,
736
data='192.168.1.1',
737
extra={
738
'proxied': True, # Enable CloudFlare proxy
739
'ttl': 1 # Automatic TTL when proxied
740
}
741
)
742
```
743
744
Check provider-specific documentation for additional capabilities available through the `extra` parameter.