Please note that the contents of this offline web site may be out of date. To access the most recent documentation visit the online version .
Note that links that point to online resources are green in color and will open in a new window.
We would love it if you could give us feedback about this material by filling this form (You have to be online to fill it)



Migrating a BIND Zone to Cloud DNS (Python)

This guide demonstrates how to use Python to parse a BIND zone file, create a matching zone and call the Changes.create method by using the Python client library. For simplicity, this example uses an OAuth 2.0 service account to minimize the code required to demonstrate this process.

Example.com BIND zone file

The following BIND DNS zone file is a simple example that uses some common practices, such as using the @ symbol to refer to the domain. This file will be migrated by using a Python script in subsequent sections.

example.com.zone :

$TTL 36000
example.com. IN      SOA     ns1.example.com. hostmaster.example.com. (
                        2005081201      ; serial
                        28800           ; refresh (8 hours)
                        1800            ; retry (30 mins)
                        2592000         ; expire (30 days)
                        86400 )         ; minimum (1 day)

example.com.            86400   NS      ns1.example.com.
example.com.            86400   NS      ns2.example.com.
example.com.            86400   MX 10   mail.example.com.
example.com.            86400   MX 20   mail2.example.com.
example.com.            86400   A       192.168.10.10
ns1.example.com.        86400   A       192.168.1.10
ns2.example.com.        86400   A       192.168.1.20
mail.example.com.       86400   A       192.168.2.10
mail2.example.com.      86400   A       192.168.2.20
www2.example.com.       86400   A    192.168.10.20
www                     86400 CNAME     @
ftp.example.com.        86400 CNAME     @
webmail.example.com.    86400 CNAME     example.com.

The Google Cloud DNS API differs somewhat from the BIND syntax, for more information see BIND syntax differences . The Python script accounts for these syntax differences.

Step 1: Configure your project and create a service account

Create or use an existing Google Developers Console project and enable the API:

  1. If you don't already have one, sign up for a Google account .
  2. Enable the Google Cloud DNS API in the Developers Console . You can choose an existing Compute Engine or App Engine project, or you can create a new project.
  3. If you need to make requests to the REST API, you will need to create an OAuth 2.0 service account client ID:
    1. On the Credentials page in the APIs & Auth section, click Create new client ID .
    2. Choose Service account .
    3. Click Create Client ID .
    4. You are prompted to download a private key file. Save this file to a location on your computer that your Python script can reference.
    5. Click Okay, got it to close the dialog.
  4. Note the following information in the project that you will need to input in later steps:
    • The service account email address ([email protected]).
    • The project ID that you wish to use. You can find the ID at the top of the Overview page in the Developers Console. You could also ask your user to provide the project name that they want to use in your app.

Step 2: Verify domain ownership

You should verify ownership of your domain when creating a zone in the Cloud DNS API. Because this example uses a service account, you will want to add the service account email address as a verified owner to your domain in Google Webmaster Tools ; otherwise, the script might encounter an error when creating the managed zone.

Step 3: Configure the script

The Python script below has the following prerequisites:

Save the script below as migrateZone.py and edit the values for the SERVICE_ACCOUNT_EMAIL and SERVICE_ACCOUNT_PKCS12_FILE_PATH variables.

import dns.zone
from dns.zone import NoSOA
from dns.exception import DNSException
from dns.rdataclass import *
from dns.rdatatype import *
from dns import rdatatype
import httplib2
import json
import sys

from apiclient.discovery import build

from oauth2client.client import SignedJwtAssertionCredentials

# Update SERVICE_ACCOUNT_EMAIL with the email address of the service account for
# the client id created in the developer console.
SERVICE_ACCOUNT_EMAIL = '<some-id>@developer.gserviceaccount.com'

# Update SERVICE_ACCOUNT_PKCS12_FILE_PATH with the file path to the private key
# file downloaded from the developer console.
SERVICE_ACCOUNT_PKCS12_FILE_PATH = '/path/to/<public_key_fingerprint>-privatekey.p12'

# Scopes for access to data.
SCOPES = ['https://www.googleapis.com/auth/ndev.clouddns.readwrite']

# See https://developers.google.com/cloud-dns/what-is-cloud-dns#supported_record_types
SUPPORTED_RECORD_TYPES = [A, AAAA, CNAME, MX, PTR, SPF, SRV, TXT, DS]

def parseZone():
  jsonOutput = {}
  additions = []

  zone_file = '%s.zone' % domain

  try:
    zone = dns.zone.from_file(zone_file, domain)

    for name, node in zone.nodes.items():
      rdatasets = node.rdatasets

      for rdataset in rdatasets:

        api_name = qualifyName(name)

        if rdataset.rdtype in SUPPORTED_RECORD_TYPES:
          addition = {
                      'name' : api_name,
                      'ttl' : str(rdataset.ttl),
                      'type' : rdatatype.to_text(rdataset.rdtype),
                      'kind' : 'dns#resourceRecordSet'
                      }
        elif rdataset.rdtype in [SOA,NS]:
          # Skip the SOA and NS records in this example. The
          # SOA record is generated as part of the managed zone
          # the NS records here aren't applicable because this
          # zone is using Google Cloud DNS rather than providing
          # its own name servers
          # In your situation, you might want to keep the NS
          # records, if so add them to the SUPPORTED_RECORD_TYPES
          continue

        # Array of records for this name/type combination
        rrdatas = []

        for rdata in rdataset:
          if rdataset.rdtype == MX:
            rrdatas.append('%s %s' % (rdata.preference, qualifyName(rdata.exchange)))
          if rdataset.rdtype == CNAME:
            rrdatas.append(qualifyName(rdata.target))
          if rdataset.rdtype == A:
            rrdatas.append(rdata.address)

        addition.update({'rrdatas' : rrdatas})
        additions.append(addition)

    jsonOutput.update({'additions' : additions })
    return jsonOutput

  except DNSException, e:
    if e.__class__ is NoSOA:
      print ('Check that your SOA line starts with a qualified domain and is in the form of: \n')
      print ('  example.com. IN      SOA     ns1.example.com. hostmaster.example.com. (')
    print e.__class__, e

def qualifyName(dnsName):
  dnsName = str(dnsName)
  if domain not in dnsName and dnsName != '@':
    return dnsName + '.' + domain + '.'
  else:
    # Catches the @ symbol case too.
    return domain + '.'

def authenticate():
  """Build and return a DNS service object authorized with the
  service accounts that act on behalf of the given user.

  Returns:
    DNS service object.
  """

  f = open(SERVICE_ACCOUNT_PKCS12_FILE_PATH, 'rb')
  key = f.read()
  f.close()

  # Setting the sub field with USER_EMAIL allows you to make API calls using the
  # special keyword 'me' in place of a user id for that user.
  credentials = SignedJwtAssertionCredentials(
      SERVICE_ACCOUNT_EMAIL,
      key,
      scope=SCOPES)
  http = httplib2.Http()
  http = credentials.authorize(http)

  # Create and return the DNS service object
  return build('dns', 'v1beta1', http=http)

def createZone(service):
  body = {
    'name' : domain.replace('.', '-'),
    'dnsName' : domain + '.',
    'description' : 'A generated zone for ' + domain
  }
  args = { 'project' : project, 'body' : body }

  try:
    response = service.managedZones().create(**args).execute()
    return response['name']
  except:
    if 'entity.managedZone\' already exists' in str(sys.exc_info()):
      print('Zone already exists, moving on.')
      return domain.replace('.','-')
    else:
      print str(sys.exc_info())
      exit()

def createRecords(service, zone_name, records):
  args = {
    'project' : project,
    'managedZone' : zone_name,
    'body' : records
  }
  try:
    response = service.changes().create(**args).execute()
    print 'Record set created, result object: '
    print json.dumps(response,
                    sort_keys=True,
                    indent=4,
                    separators=(',', ': '))
  except:
    print str(sys.exc_info())
    exit()

if __name__ == '__main__':
    if len(sys.argv) > 2:
      project = sys.argv[1]
      domain = sys.argv[2]
      service = authenticate()
      records = parseZone()
      zone_name = createZone(service)
      createRecords(service, zone_name, records)
    else:
      print ('Missing arguments. Provide both the Developer Console project ID'
             'and domain name. ')
      print ('Usage: parseZone.py console-project-name example.com')

Step 4: Run the script

The script assumes that it is being run within the same directory as your BIND zone files and that your zone files are named in the example.com.zone format. You can change this pattern in the script if needed.

The script performs the following actions:

  1. Parses the BIND zone file, ignores the SOA and NS records because Cloud DNS API provides those and also ignores any unsupported record types. You might want to check your SOA record and determine if you need to migrate any values, such as the administrator value, to the zone. If so, you can run the gcloud dns records --zone="<your-zone-name>" edit command to quickly make the SOA record edits.
  2. Creates a managed zone for this origin.
  3. Creates records in this managed zone based on the parsed zone file.

To run the script, provide your Developers Console project ID and domain as arguments:

cd <path to zone files>
python migrateZone.py <project_name> example.com

Check the status of the request

You can check if the status of the record changes from the script by using the change ID that is printed to the console with the following command:

gcloud dns changes --zone="<your-zone-name>" get <change_id>

Next steps

At this point, your Cloud DNS name servers are serving the zone data you configured on port 53 to anyone who asks. However, users of the DNS do not yet know that they should ask these name servers for all questions about example.com . To do this, you need to configure NS records for example.com in your registrar. See Updating your domain's name servers to use Cloud DNS for more information.

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.