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:
- If you don't already have one, sign up for a Google account .
- 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.
-
If you need to make requests to the REST API, you will need to create an
OAuth 2.0 service account client ID:
- On the Credentials page in the APIs & Auth section, click Create new client ID .
- Choose Service account .
- Click Create Client ID .
- You are prompted to download a private key file. Save this file to a location on your computer that your Python script can reference.
- Click Okay, got it to close the dialog.
-
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:
-
Google API client library for Python
$ pip install --upgrade google-api-python-client
-
$ pip install dnspython
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:
-
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 thegcloud dns records --zone="<your-zone-name>" edit
command to quickly make theSOA
record edits. - Creates a managed zone for this origin.
- 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.