Generating DNS-zones from the Digital Ocean API

When you’re managing an ever-changing machine infrastructure in a public cloud, it is easy to forget to add new and remove old machines from your DNS.

At PowerDNS, we had a demonstration setup that was rebuilt several times in the span of two weeks, and I got tired of editing the zonefile* by hand, so let’s automate the hell out of it.

Fortunately, Digital Ocean has an API that allows you to retrieve Droplet (VPS) information. This API has bindings in several languages, so it’s easy to generate these DNS-entries.

Here’s the quick ‘n dirty python script I whipped up:

#!/usr/bin/env python2
import sys
import time
import os

try:
    import json
except ImportError:
    import simplejson as json

try:
    from dopy.manager import DoError, DoManager
except ImportError as e:
    print("dopy library required for this script")
    sys.exit(1)

conf = {}
################################################################################
# Change these settings
################################################################################
conf['APIkey'] = os.environ.get('API_KEY', 'changeme')
conf['zone'] = os.environ.get('ZONE', 'thisiswrong.example')
conf['ns'] = ['ns1.%s' % conf.get('zone'), 'ns2.%s' % conf.get('zone')]
conf['SOA'] = "%s. hostmaster.%s. %s 360 60 8640 360" % (conf.get('ns')[0], conf.get('zone'), int(time.time()))

################################################################################
# Begin script
################################################################################
manager = DoManager(None, conf.get('APIkey'), api_version=2)
droplets = manager.all_active_droplets()

sys.stdout.write("$ORIGIN %s.\n" % conf.get('zone'))
sys.stdout.write("$TTL 120\n")
sys.stdout.write("\n")
sys.stdout.write("@\tIN\tSOA\t%s\n" % conf.get('SOA'))
for ns in conf.get('ns'):
    sys.stdout.write("@\tIN\tNS\t%s.\n" % ns)
sys.stdout.write("\n")

for droplet in droplets:
    if not droplet['name'].endswith('.%s' % conf.get('zone')):
        continue

    name = droplet['name'].split('.')[0]

    for net in droplet['networks']['v4']:
        if net['type'] == 'public':
            sys.stdout.write("%s\tIN\tA\t%s\n" % (name, net['ip_address']))

    for net in droplet['networks']['v6']:
        if net['type'] == 'public':
            sys.stdout.write("%s\tIN\tAAAA\t%s\n" % (name, net['ip_address']))

You’ll need the dopy and six(required by dopy) python modules installed.

The easiest way to generate the zone is through cron:

*/5 * * * * ZONE=myzone.example; API_KEY=s3cr1t; /usr/local/bin/do-zone-generator.py > /tmp/$ZONE.zone && cp /tmp/$ZONE.zone /var/lib/powerdns/zones && pdns_control bind-reload-now $ZONE >/dev/null

This will generate the zone, copy it to the right place and tell PowerDNS to reload the zone (sending out NOTIFYs).

Of course, the script could be extended to insert the data into a database-backed PowerDNS using the PowerDNS API. But this is left as an exercise to the reader.

*: As a sidenote, even though the PowerDNS Authoritative Server is deployed most of the time because of its excellent database backends, for my own small-scale deployments, I prefer zone-files over a database. But this might change, as pdnsutil supports an edit-zone command that will create a faux zonefile from the zone in the database, open it in $EDITOR and will insert it into the database after editing. See Bert Hubert’s blogpost on this.