CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-apache-libcloud

A standard Python library that abstracts away differences among multiple cloud provider APIs

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

dns-management.mddocs/

DNS Management

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.

Providers

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 providers

Driver Factory

from 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 enum

Returns:

  • Driver class for the specified provider

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')

Core Classes

DNSDriver

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 account
  • create_zone(): Create a new DNS zone
  • list_records(): List DNS records in a zone
  • create_record(): Create a new DNS record
  • update_record(): Update an existing DNS record
  • delete_record(): Delete a DNS record

Zone

class 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) -> Zone

Represents a DNS zone (domain) that contains DNS records.

Properties:

  • id: Unique zone identifier
  • domain: Domain name (e.g., "example.com")
  • type: Zone type ("master", "slave", etc.)
  • ttl: Time-to-live in seconds
  • extra: Provider-specific metadata

Methods:

  • list_records(): List records in this zone
  • create_record(): Create a record in this zone
  • delete(): Delete the entire zone

Record

class 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) -> bool

Represents a DNS record within a zone.

Properties:

  • id: Unique record identifier
  • name: Record name (subdomain or "@" for root)
  • type: Record type (A, AAAA, CNAME, etc.)
  • data: Record data/value
  • zone: Parent zone
  • ttl: Time-to-live in seconds
  • extra: Provider-specific metadata

Methods:

  • update(): Update this record
  • delete(): Delete this record

RecordType

class 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.

Usage Examples

Basic Zone Management

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}")

DNS Record Management

# 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")

Record Operations Using Zone Methods

# 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}")

Advanced Record Management

# 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")

Bulk Operations and Management

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)

Multi-Provider DNS Management

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')

DNS Record Monitoring and Health Checks

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']})")

Zone Backup and Restore

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')

Exception Handling

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")

Provider-Specific Features

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

docs

backup-services.md

compute-services.md

container-services.md

core-driver-system.md

dns-management.md

index.md

load-balancer-services.md

storage-services.md

tile.json