A standard Python library that abstracts away differences among multiple cloud provider APIs
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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.
from libcloud.dns.types import Provider
class Provider:
"""Enumeration of supported DNS providers"""
ROUTE53 = 'route53'
RACKSPACE = 'rackspace'
ZERIGO = 'zerigo'
DNSIMPLE = 'dnsimple'
LINODE = 'linode'
DNSPARK = 'dnspark'
CLOUDFLARE = 'cloudflare'
GOOGLE = 'google'
GANDIAPI = 'gandiapi'
POINTDNS = 'pointdns'
RCODEZERO = 'rcodezero'
NFSN = 'nfsn'
NSONE = 'nsone'
LIQUIDWEB = 'liquidweb'
# ... more providersfrom libcloud.dns.providers import get_driver
def get_driver(provider: Provider) -> type[DNSDriver]Get the driver class for a specific DNS provider.
Parameters:
provider: Provider identifier from the Provider enumReturns:
Example:
from libcloud.dns.types import Provider
from libcloud.dns.providers import get_driver
# Get Route 53 driver class
cls = get_driver(Provider.ROUTE53)
# Initialize driver with credentials
driver = cls('access_key', 'secret_key')class DNSDriver(BaseDriver):
"""Base class for all DNS drivers"""
def list_zones(self) -> List[Zone]
def get_zone(self, zone_id: str) -> Zone
def create_zone(self, domain: str, type: str = 'master', ttl: int = None, extra: Dict = None) -> Zone
def update_zone(self, zone: Zone, domain: str = None, type: str = None, ttl: int = None, extra: Dict = None) -> Zone
def delete_zone(self, zone: Zone) -> bool
def list_records(self, zone: Zone, type: RecordType = None) -> List[Record]
def get_record(self, zone_id: str, record_id: str) -> Record
def create_record(self, name: str, zone: Zone, type: RecordType, data: str, extra: Dict = None) -> Record
def update_record(self, record: Record, name: str = None, type: RecordType = None, data: str = None, extra: Dict = None) -> Record
def delete_record(self, record: Record) -> bool
def ex_create_multi_value_record(self, name: str, zone: Zone, type: RecordType, data: List[str], extra: Dict = None) -> List[Record]Base class that all DNS drivers inherit from. Provides methods for managing DNS zones and records.
Key Methods:
list_zones(): List all DNS zones in the accountcreate_zone(): Create a new DNS zonelist_records(): List DNS records in a zonecreate_record(): Create a new DNS recordupdate_record(): Update an existing DNS recorddelete_record(): Delete a DNS recordclass Zone:
"""Represents a DNS zone/domain"""
id: str
domain: str
type: str
ttl: int
driver: DNSDriver
extra: Dict[str, Any]
def list_records(self, type: RecordType = None) -> List[Record]
def create_record(self, name: str, type: RecordType, data: str, extra: Dict = None) -> Record
def update_record(self, record: Record, name: str = None, type: RecordType = None, data: str = None, extra: Dict = None) -> Record
def delete_record(self, record: Record) -> bool
def delete(self) -> bool
def update(self, domain: str = None, type: str = None, ttl: int = None, extra: Dict = None) -> ZoneRepresents a DNS zone (domain) that contains DNS records.
Properties:
id: Unique zone identifierdomain: Domain name (e.g., "example.com")type: Zone type ("master", "slave", etc.)ttl: Time-to-live in secondsextra: Provider-specific metadataMethods:
list_records(): List records in this zonecreate_record(): Create a record in this zonedelete(): Delete the entire zoneclass Record:
"""Represents a DNS record"""
id: str
name: str
type: RecordType
data: str
zone: Zone
driver: DNSDriver
ttl: int
extra: Dict[str, Any]
def update(self, name: str = None, type: RecordType = None, data: str = None, extra: Dict = None) -> Record
def delete(self) -> boolRepresents a DNS record within a zone.
Properties:
id: Unique record identifiername: Record name (subdomain or "@" for root)type: Record type (A, AAAA, CNAME, etc.)data: Record data/valuezone: Parent zonettl: Time-to-live in secondsextra: Provider-specific metadataMethods:
update(): Update this recorddelete(): Delete this recordclass RecordType:
"""DNS record types enumeration"""
A = 'A'
AAAA = 'AAAA'
CNAME = 'CNAME'
MX = 'MX'
NS = 'NS'
PTR = 'PTR'
SOA = 'SOA'
SRV = 'SRV'
TXT = 'TXT'
SPF = 'SPF'
CAA = 'CAA'
DNAME = 'DNAME'
NAPTR = 'NAPTR'
HINFO = 'HINFO'
SSHFP = 'SSHFP'
TLSA = 'TLSA'Enumeration of supported DNS record types.
from libcloud.dns.types import Provider, RecordType
from libcloud.dns.providers import get_driver
# Initialize driver
cls = get_driver(Provider.ROUTE53)
driver = cls('access_key', 'secret_key')
# List existing zones
zones = driver.list_zones()
for zone in zones:
print(f"Zone: {zone.domain} (Type: {zone.type}, TTL: {zone.ttl})")
# Create a new zone
zone = driver.create_zone(
domain='example.com',
type='master',
ttl=3600
)
print(f"Created zone: {zone.domain} ({zone.id})")
# Get a specific zone
try:
zone = driver.get_zone('Z123456789')
print(f"Found zone: {zone.domain}")
except Exception as e:
print(f"Zone not found: {e}")# List all records in a zone
records = driver.list_records(zone)
print(f"Zone {zone.domain} has {len(records)} records")
for record in records:
print(f" {record.name} {record.type} {record.data} (TTL: {record.ttl})")
# Create different types of records
# A record
a_record = driver.create_record(
name='www',
zone=zone,
type=RecordType.A,
data='192.168.1.100'
)
print(f"Created A record: {a_record.name}.{zone.domain} -> {a_record.data}")
# CNAME record
cname_record = driver.create_record(
name='blog',
zone=zone,
type=RecordType.CNAME,
data='www.example.com.'
)
print(f"Created CNAME record: {cname_record.name}.{zone.domain} -> {cname_record.data}")
# MX record
mx_record = driver.create_record(
name='@', # Root domain
zone=zone,
type=RecordType.MX,
data='10 mail.example.com.',
extra={'priority': 10} # Some providers store priority in extra
)
print(f"Created MX record with priority 10")
# TXT record
txt_record = driver.create_record(
name='@',
zone=zone,
type=RecordType.TXT,
data='v=spf1 include:_spf.google.com ~all'
)
print(f"Created TXT record for SPF")# Alternative way using zone methods
zone = driver.get_zone('Z123456789')
# Create record using zone method
record = zone.create_record(
name='api',
type=RecordType.A,
data='10.0.1.50',
extra={'ttl': 300} # 5 minute TTL
)
# List records in zone using zone method
records = zone.list_records(type=RecordType.A)
print(f"Found {len(records)} A records")
# Update a record
updated_record = record.update(
data='10.0.1.51', # New IP address
extra={'ttl': 600} # New TTL
)
print(f"Updated record: {updated_record.data}")
# Delete a record
success = record.delete()
print(f"Record deleted: {success}")# Create multiple A records for load balancing (if supported)
try:
records = driver.ex_create_multi_value_record(
name='lb',
zone=zone,
type=RecordType.A,
data=['192.168.1.10', '192.168.1.11', '192.168.1.12']
)
print(f"Created {len(records)} load balancer records")
except AttributeError:
# Fallback: create individual records
ips = ['192.168.1.10', '192.168.1.11', '192.168.1.12']
for i, ip in enumerate(ips):
record = driver.create_record(
name='lb',
zone=zone,
type=RecordType.A,
data=ip
)
print(f"Created LB record {i+1}: {record.data}")
# Create SRV record for services
srv_record = driver.create_record(
name='_http._tcp',
zone=zone,
type=RecordType.SRV,
data='10 5 80 web.example.com.',
extra={'priority': 10, 'weight': 5, 'port': 80}
)
print(f"Created SRV record for HTTP service")
# Create CAA record for certificate authority authorization
caa_record = driver.create_record(
name='@',
zone=zone,
type=RecordType.CAA,
data='0 issue "letsencrypt.org"'
)
print(f"Created CAA record for Let's Encrypt")def setup_basic_dns(driver, domain, config):
"""Setup basic DNS records for a domain"""
# Create or get zone
try:
zone = None
for z in driver.list_zones():
if z.domain == domain:
zone = z
break
if not zone:
zone = driver.create_zone(domain, ttl=3600)
print(f"Created zone: {domain}")
except Exception as e:
print(f"Error creating zone: {e}")
return
# Create basic records
records_to_create = [
# A records
('www', RecordType.A, config['web_ip']),
('mail', RecordType.A, config['mail_ip']),
('ftp', RecordType.A, config['ftp_ip']),
# CNAME records
('blog', RecordType.CNAME, f'www.{domain}.'),
('shop', RecordType.CNAME, f'www.{domain}.'),
# MX record
('@', RecordType.MX, f"10 mail.{domain}."),
# TXT records
('@', RecordType.TXT, config['spf_record']),
('_dmarc', RecordType.TXT, config['dmarc_record']),
]
created_records = []
for name, record_type, data in records_to_create:
try:
record = driver.create_record(
name=name,
zone=zone,
type=record_type,
data=data
)
created_records.append(record)
print(f"Created: {name} {record_type} {data}")
except Exception as e:
print(f"Failed to create {name} {record_type}: {e}")
return zone, created_records
# Usage
dns_config = {
'web_ip': '192.168.1.100',
'mail_ip': '192.168.1.101',
'ftp_ip': '192.168.1.102',
'spf_record': 'v=spf1 include:_spf.google.com ~all',
'dmarc_record': 'v=DMARC1; p=quarantine; rua=mailto:dmarc@example.com'
}
zone, records = setup_basic_dns(driver, 'example.com', dns_config)from libcloud.dns.types import Provider
from libcloud.dns.providers import get_driver
# Configure multiple DNS providers
dns_providers = {
'route53': {
'driver': get_driver(Provider.ROUTE53),
'credentials': ('aws_access_key', 'aws_secret_key')
},
'cloudflare': {
'driver': get_driver(Provider.CLOUDFLARE),
'credentials': ('email', 'api_key')
}
}
# Initialize drivers
drivers = {}
for name, config in dns_providers.items():
cls = config['driver']
drivers[name] = cls(*config['credentials'])
# Compare DNS records across providers
def compare_dns_records(domain):
"""Compare DNS records for a domain across providers"""
provider_records = {}
for provider_name, driver in drivers.items():
try:
zones = driver.list_zones()
zone = None
for z in zones:
if z.domain == domain:
zone = z
break
if zone:
records = driver.list_records(zone)
provider_records[provider_name] = {
(r.name, r.type): r.data for r in records
}
print(f"{provider_name}: {len(records)} records")
else:
print(f"{provider_name}: Domain not found")
provider_records[provider_name] = {}
except Exception as e:
print(f"Error accessing {provider_name}: {e}")
provider_records[provider_name] = {}
# Find differences
all_record_keys = set()
for records in provider_records.values():
all_record_keys.update(records.keys())
print(f"\nRecord comparison for {domain}:")
for key in sorted(all_record_keys):
name, record_type = key
values = []
for provider in provider_records:
value = provider_records[provider].get(key, 'MISSING')
values.append(f"{provider}: {value}")
print(f" {name} {record_type}: {', '.join(values)}")
# Usage
compare_dns_records('example.com')import socket
import time
from typing import Dict, List
def verify_dns_propagation(domain: str, record_name: str, record_type: str, expected_value: str, nameservers: List[str] = None) -> Dict[str, str]:
"""Verify DNS record propagation across nameservers"""
import dns.resolver
if not nameservers:
nameservers = ['8.8.8.8', '1.1.1.1', '208.67.222.222'] # Google, Cloudflare, OpenDNS
results = {}
fqdn = f"{record_name}.{domain}" if record_name != '@' else domain
for ns in nameservers:
try:
resolver = dns.resolver.Resolver()
resolver.nameservers = [ns]
answers = resolver.query(fqdn, record_type)
actual_value = str(answers[0])
results[ns] = {
'value': actual_value,
'matches': actual_value == expected_value,
'status': 'OK' if actual_value == expected_value else 'MISMATCH'
}
except Exception as e:
results[ns] = {
'value': None,
'matches': False,
'status': f'ERROR: {e}'
}
return results
def monitor_dns_changes(driver, zone, record_name: str, check_interval: int = 60):
"""Monitor DNS record changes"""
print(f"Monitoring DNS changes for {record_name}.{zone.domain}")
last_values = {}
while True:
try:
records = driver.list_records(zone)
current_values = {}
for record in records:
if record.name == record_name:
key = f"{record.name}_{record.type}"
current_values[key] = record.data
# Check for changes
for key, value in current_values.items():
if key in last_values and last_values[key] != value:
print(f"CHANGE DETECTED: {key} changed from {last_values[key]} to {value}")
elif key not in last_values:
print(f"NEW RECORD: {key} = {value}")
# Check for deletions
for key in last_values:
if key not in current_values:
print(f"RECORD DELETED: {key} was {last_values[key]}")
last_values = current_values.copy()
except Exception as e:
print(f"Error monitoring DNS: {e}")
time.sleep(check_interval)
# Usage
zone = driver.get_zone('Z123456789')
# Verify propagation after creating a record
record = driver.create_record('test', zone, RecordType.A, '192.168.1.50')
time.sleep(10) # Wait a bit for propagation
propagation_results = verify_dns_propagation('example.com', 'test', 'A', '192.168.1.50')
for ns, result in propagation_results.items():
print(f"Nameserver {ns}: {result['status']} ({result['value']})")import json
from datetime import datetime
def backup_zone(driver, zone) -> Dict:
"""Create a backup of all records in a zone"""
backup_data = {
'zone': {
'domain': zone.domain,
'type': zone.type,
'ttl': zone.ttl,
'extra': zone.extra
},
'records': [],
'backup_timestamp': datetime.now().isoformat()
}
records = driver.list_records(zone)
for record in records:
backup_data['records'].append({
'name': record.name,
'type': record.type,
'data': record.data,
'ttl': record.ttl,
'extra': record.extra
})
print(f"Backed up {len(records)} records from {zone.domain}")
return backup_data
def restore_zone(driver, backup_data: Dict, domain: str = None):
"""Restore a zone from backup data"""
domain = domain or backup_data['zone']['domain']
# Create zone if it doesn't exist
zone = None
try:
zones = driver.list_zones()
for z in zones:
if z.domain == domain:
zone = z
break
if not zone:
zone = driver.create_zone(
domain=domain,
type=backup_data['zone']['type'],
ttl=backup_data['zone']['ttl']
)
print(f"Created zone: {domain}")
except Exception as e:
print(f"Error creating zone: {e}")
return
# Restore records
restored_count = 0
for record_data in backup_data['records']:
try:
record = driver.create_record(
name=record_data['name'],
zone=zone,
type=record_data['type'],
data=record_data['data'],
extra=record_data.get('extra', {})
)
restored_count += 1
except Exception as e:
print(f"Failed to restore record {record_data['name']}: {e}")
print(f"Restored {restored_count} records to {domain}")
# Usage
zone = driver.get_zone('Z123456789')
# Create backup
backup = backup_zone(driver, zone)
# Save backup to file
with open(f'dns_backup_{zone.domain}_{datetime.now().strftime("%Y%m%d_%H%M%S")}.json', 'w') as f:
json.dump(backup, f, indent=2)
# Restore from backup (to same or different domain)
restore_zone(driver, backup, 'new-example.com')from libcloud.dns.types import (
ZoneError,
RecordError,
ZoneDoesNotExistError,
RecordDoesNotExistError,
ZoneAlreadyExistsError,
RecordAlreadyExistsError
)
class ZoneError(LibcloudError):
"""Base zone exception"""
class RecordError(LibcloudError):
"""Base record exception"""
class ZoneDoesNotExistError(ZoneError):
"""Zone does not exist"""
class RecordDoesNotExistError(RecordError):
"""Record does not exist"""
class ZoneAlreadyExistsError(ZoneError):
"""Zone already exists"""
class RecordAlreadyExistsError(RecordError):
"""Record already exists"""Error Handling Example:
from libcloud.dns.types import ZoneDoesNotExistError, RecordAlreadyExistsError
from libcloud.common.types import InvalidCredsError
try:
# Attempt to get a zone
zone = driver.get_zone('nonexistent-zone-id')
except ZoneDoesNotExistError:
print("Zone not found, creating new zone...")
zone = driver.create_zone('example.com')
try:
# Attempt to create a record
record = driver.create_record('www', zone, RecordType.A, '192.168.1.1')
except RecordAlreadyExistsError:
print("Record already exists, updating instead...")
existing_records = driver.list_records(zone, type=RecordType.A)
for record in existing_records:
if record.name == 'www':
record.update(data='192.168.1.1')
break
except InvalidCredsError:
print("Invalid DNS provider credentials")Different providers offer additional features through the ex_* parameter pattern:
# Route 53 specific features
route53_driver = get_driver(Provider.ROUTE53)('access_key', 'secret_key')
# Create record with Route 53 specific options
record = route53_driver.create_record(
name='www',
zone=zone,
type=RecordType.A,
data='192.168.1.1',
extra={
'ttl': 300,
'health_check_id': 'hc-123456', # Health check integration
'set_identifier': 'primary', # For routing policies
'weight': 100 # Weighted routing
}
)
# CloudFlare specific features
cf_driver = get_driver(Provider.CLOUDFLARE)('email', 'api_key')
# Create record with CloudFlare proxy enabled
record = cf_driver.create_record(
name='www',
zone=zone,
type=RecordType.A,
data='192.168.1.1',
extra={
'proxied': True, # Enable CloudFlare proxy
'ttl': 1 # Automatic TTL when proxied
}
)Check provider-specific documentation for additional capabilities available through the extra parameter.
Install with Tessl CLI
npx tessl i tessl/pypi-apache-libcloud