2013-10-12 16:34:33 +00:00
#!/usr/bin/env python
2013-06-03 05:29:45 +00:00
# Author: Colin Johnson / colin@cloudavail.com
2013-10-12 16:34:33 +00:00
# Date: 2013-10-12
# Version 0.2
2013-06-03 05:29:45 +00:00
# License Type: GNU GENERAL PUBLIC LICENSE, Version 3
2013-10-12 16:34:33 +00:00
import argparse # used to gather user input
2013-06-03 05:29:45 +00:00
import ConfigParser # import ConfigParser - used for getting configuration
2013-06-09 02:47:20 +00:00
import logging # used to write out log events - events that are neither required output nor error
2013-10-12 16:34:33 +00:00
import os # used to get app_name
import re # import re used to find/replace zone
2013-06-03 05:29:45 +00:00
2013-10-12 16:34:33 +00:00
import boto . route53 # import boto.route53 (not just boto) - need to import correct module
2013-06-03 05:29:45 +00:00
2013-06-08 15:30:17 +00:00
def commit_record_changeset ( destination_zone_record_changeset ) :
2013-10-12 16:34:33 +00:00
''' commit_record_changeset commits records to AWS '''
2013-06-03 05:29:45 +00:00
try :
2013-06-08 15:30:17 +00:00
destination_zone_record_changeset . commit ( )
2013-06-03 05:29:45 +00:00
except boto . route53 . exception . DNSServerError , error :
2013-10-12 16:34:33 +00:00
logging . critical ( ' An error occured when attempting to commit records to the zone " {destination_zone_name!s} . " '
. format ( destination_zone_name = destination_zone_name ) )
logging . critical ( ' The error message given was: {error!s} . ' . format ( error = error . error_message ) )
2013-06-03 05:29:45 +00:00
exit ( 69 )
2013-06-09 02:47:20 +00:00
2013-06-04 20:27:07 +00:00
def diff_record ( record_a , record_a_object , record_b , record_b_object ) :
2013-10-12 16:34:33 +00:00
''' diff_record compares two different resource records '''
compare_values = [ ' type ' , ' ttl ' , ' resource_records ' , ' alias_hosted_zone_id ' , ' alias_dns_name ' , ' identifier ' , ' weight ' , ' region ' ]
2013-06-04 20:27:07 +00:00
diff_record_result = False
for value in compare_values :
2013-06-08 15:30:17 +00:00
if getattr ( record , value ) != getattr ( destination_zone_existing_resource_record_dict [ record . name ] , value ) :
2013-06-04 20:27:07 +00:00
diff_record_result = True
return diff_record_result
2013-10-12 16:34:33 +00:00
2013-06-03 05:29:45 +00:00
app_name = os . path . basename ( __file__ )
2013-06-09 02:47:20 +00:00
parser = argparse . ArgumentParser ( )
2013-10-12 16:34:33 +00:00
parser . add_argument ( ' --log-level ' , dest = ' loglevel ' , help = str ( ' set the log level when running {app_name!s} . ' . format ( app_name = app_name ) ) ,
default = ' WARNING ' , choices = [ ' DEBUG ' , ' INFO ' , ' WARNING ' , ' ERROR ' , ' CRITICAL ' ] )
parser . add_argument ( ' --config ' , help = str ( ' choose the configuration file to be used when running {app_name!s} ' . format ( app_name = app_name ) ) ,
default = ' config.ini ' )
2013-06-09 02:47:20 +00:00
args = parser . parse_args ( )
config_file_path = args . config
2013-06-03 05:29:45 +00:00
config = ConfigParser . ConfigParser ( )
2013-06-09 02:47:20 +00:00
config . read ( config_file_path )
2013-10-12 16:34:33 +00:00
# configure logging
log_format = ' %(message)s '
log_level = str . upper ( args . loglevel )
logging . basicConfig ( level = log_level , format = log_format )
# functions: currently supports newzone - the functions are set automatically by the route53-migrate-zone script
2013-06-04 20:27:07 +00:00
functions = [ ]
2013-06-03 05:29:45 +00:00
2013-10-12 16:34:33 +00:00
# the source_zone user credentials should be read-only
source_zone_access_key = config . get ( ' source_zone_values ' , ' source_zone_access_key ' )
source_zone_secret_key = config . get ( ' source_zone_values ' , ' source_zone_secret_key ' )
source_zone_name = config . get ( ' source_zone_values ' , ' source_zone_name ' )
#
destination_zone_access_key = config . get ( ' destination_zone_values ' , ' destination_zone_access_key ' )
destination_zone_secret_key = config . get ( ' destination_zone_values ' , ' destination_zone_secret_key ' )
destination_zone_name = config . get ( ' destination_zone_values ' , ' destination_zone_name ' )
# best would be to retreive the destination_zone_id using destination_zone_name
destination_zone_id = config . get ( ' destination_zone_values ' , ' destination_zone_id ' )
2013-06-03 05:29:45 +00:00
2013-10-12 16:34:33 +00:00
record_types_to_migrate = [ ' A ' , ' CNAME ' , ' MX ' , ' TXT ' ]
2013-06-04 20:27:07 +00:00
2013-06-08 15:30:17 +00:00
if source_zone_name != destination_zone_name :
2013-10-12 16:34:33 +00:00
logging . info ( ' {app_name!s} will rewrite domain names ending in {source_zone_name!s} to domain names ending in {destination_zone_name!s} ' . format
( app_name = app_name , source_zone_name = source_zone_name , destination_zone_name = destination_zone_name ) )
functions . append ( ' newzone ' )
2013-06-04 20:27:07 +00:00
2013-10-12 16:34:33 +00:00
# creates Route53Connection Object
2013-06-08 15:30:17 +00:00
source_connection = boto . route53 . Route53Connection ( aws_access_key_id = source_zone_access_key , aws_secret_access_key = source_zone_secret_key )
destination_connection = boto . route53 . Route53Connection ( aws_access_key_id = destination_zone_access_key , aws_secret_access_key = destination_zone_secret_key )
2013-06-03 05:29:45 +00:00
2013-10-12 16:34:33 +00:00
# create connection to source_zone
2013-06-03 05:29:45 +00:00
try :
2013-06-08 15:30:17 +00:00
source_zone = source_connection . get_zone ( source_zone_name )
2013-06-03 05:29:45 +00:00
except boto . route53 . exception . DNSServerError , error :
2013-10-12 16:34:33 +00:00
logging . critical ( ' An error occured when attempting to create a connection to AWS. ' )
logging . critical ( ' The error message given was: {error!s} . ' . format ( error = error . error_message ) )
2013-06-08 15:30:17 +00:00
exit ( 69 )
2013-06-03 05:29:45 +00:00
2013-10-12 16:34:33 +00:00
# create connection to destination_zone
2013-06-04 20:27:07 +00:00
try :
2013-06-08 15:30:17 +00:00
destination_zone = destination_connection . get_zone ( destination_zone_name )
2013-06-04 20:27:07 +00:00
except boto . route53 . exception . DNSServerError , error :
2013-10-12 16:34:33 +00:00
logging . critical ( ' An error occured when attempting to create a connection to AWS. ' )
logging . critical ( ' The error message given was: {error!s} . ' . format ( error = error . error_message ) )
2013-06-08 15:30:17 +00:00
exit ( 69 )
2013-06-04 20:27:07 +00:00
2013-10-12 16:34:33 +00:00
# creates ResourceRecordSets object named source_zone_records
# (ResourceRecordSets = a collection of resource records)
2013-06-08 15:30:17 +00:00
source_zone_records = source_zone . get_records ( )
2013-10-12 16:34:33 +00:00
# creates ResourceRecordSets object named destination_zone_records
# (ResourceRecordSets = a collection of resource records)
2013-06-08 15:30:17 +00:00
destination_zone_records = destination_zone . get_records ( )
2013-06-04 20:27:07 +00:00
2013-10-12 16:34:33 +00:00
# resource_record_dict will be used to store all resource records that
# should be transferred
2013-06-03 05:29:45 +00:00
resource_record_dict = { }
2013-10-12 16:34:33 +00:00
# destination_zone_existing_resource_record_dict will be used to store all
# resource records that exist in destination zone
2013-06-08 15:30:17 +00:00
destination_zone_existing_resource_record_dict = { }
2013-06-04 20:27:07 +00:00
2013-10-12 16:34:33 +00:00
# creates a set of changes to be delivered to Route53
2013-06-08 15:30:17 +00:00
destination_zone_record_changeset = boto . route53 . record . ResourceRecordSets ( destination_connection , destination_zone_id )
2013-06-04 20:27:07 +00:00
2013-06-08 15:30:17 +00:00
for record in destination_zone_records :
destination_zone_existing_resource_record_dict [ record . name ] = record
2013-06-04 20:27:07 +00:00
2013-10-12 16:34:33 +00:00
# counts of records - should be replaced by dictionary
2013-06-08 15:30:17 +00:00
examined_record_count = 0
2013-06-04 20:27:07 +00:00
migrated_record_count = 0
2013-06-08 15:30:17 +00:00
existing_records_in_destination_zone_count = 0
identical_records_in_destination_zone_count = 0
different_records_in_destination_zone_count = 0
uncommitted_change_elements = 0
2013-06-03 05:29:45 +00:00
2013-10-12 16:34:33 +00:00
# get records from source_zone
2013-06-08 15:30:17 +00:00
for record in source_zone_records :
2013-06-03 05:29:45 +00:00
if record . type in record_types_to_migrate :
2013-10-12 16:34:33 +00:00
if ' newzone ' in functions :
2013-06-09 02:47:20 +00:00
destination_record = re . sub ( source_zone_name , destination_zone_name , record . name )
2013-10-12 16:34:33 +00:00
logging . debug ( ' Record " {record_name!s} " will be rewritten as " {destination_record!s} " . '
. format ( record_name = record . name , destination_record = destination_record ) )
2013-06-09 02:47:20 +00:00
record . name = destination_record
2013-10-12 16:34:33 +00:00
# test if record exists in destination_zone
2013-06-08 15:30:17 +00:00
if record . name in destination_zone_existing_resource_record_dict :
existing_records_in_destination_zone_count + = 1
2013-10-12 16:34:33 +00:00
# compare records in source_domain and destination_domain, store result as diff_result
2013-06-08 15:30:17 +00:00
diff_result = diff_record ( record . name , record , record . name , destination_zone_existing_resource_record_dict )
2013-06-04 20:27:07 +00:00
if diff_result is True :
2013-06-08 15:30:17 +00:00
different_records_in_destination_zone_count + = 1
2013-10-12 16:34:33 +00:00
logging . info ( ' Record {record_name!s} exists in source zone {source_zone_name!s} and destination zone {destination_zone_name!s} and is different. '
. format ( record_name = record . name , source_zone_name = source_zone_name , destination_zone_name = destination_zone_name ) )
2013-06-04 20:27:07 +00:00
elif diff_result is False :
2013-06-08 15:30:17 +00:00
identical_records_in_destination_zone_count + = 1
2013-10-12 16:34:33 +00:00
logging . info ( ' Record {record_name!s} exists in source zone {source_zone_name!s} and destination zone {destination_zone_name!s} and is identical. '
. format ( record_name = record . name , source_zone_name = source_zone_name , destination_zone_name = destination_zone_name ) )
2013-06-04 20:27:07 +00:00
else :
2013-10-12 16:34:33 +00:00
logging . critical ( ' Diff of record {record_name!s} failed. '
. format ( record_name = record . name ) )
2013-06-08 15:30:17 +00:00
exit ( 70 )
2013-06-04 20:27:07 +00:00
else :
resource_record_dict [ record . name ] = boto . route53 . record . Record ( name = record . name , type = record . type , ttl = record . ttl , resource_records = record . resource_records , alias_hosted_zone_id = record . alias_hosted_zone_id , alias_dns_name = record . alias_dns_name , identifier = record . identifier , weight = record . weight , region = record . region )
2013-06-03 05:29:45 +00:00
for record in resource_record_dict :
2013-06-08 15:30:17 +00:00
examined_record_count + = 1
2013-06-03 05:29:45 +00:00
#if record is an alias record we are not supporting yet
if resource_record_dict [ record ] . alias_dns_name is not None :
2013-10-12 16:34:33 +00:00
logging . info ( ' Record {record_name!s} is an alias record set and will not be migrated {app_name!s} does not currently support alias record sets. '
. format ( record_name = resource_record_dict [ record ] . name , app_name = app_name ) )
2013-06-03 05:29:45 +00:00
else :
uncommitted_change_elements + = 1
2013-06-08 15:30:17 +00:00
destination_zone_record_changeset . add_change_record ( " CREATE " , resource_record_dict [ record ] )
2013-10-12 16:34:33 +00:00
logging . info ( ' Uncommitted Record Count: {uncommitted_change_elements!s} '
. format ( uncommitted_change_elements = uncommitted_change_elements ) )
# if there are 99 uncomitted change elements than they must be committed - Amazon only accepts up to 99 change elements at a given time
# if the number of examined records is equal to the number of records then we can commit as well - we are now done examing records
2013-06-08 15:30:17 +00:00
if uncommitted_change_elements > = 99 or examined_record_count == len ( resource_record_dict ) :
2013-10-12 16:34:33 +00:00
logging . info ( ' Flushing this Number of Uncommitted Records: {uncommitted_change_elements!s} '
. format ( uncommitted_change_elements = uncommitted_change_elements ) )
2013-06-08 15:30:17 +00:00
commit_record_changeset ( destination_zone_record_changeset )
2013-06-03 05:29:45 +00:00
migrated_record_count + = uncommitted_change_elements
uncommitted_change_elements = 0
2013-06-08 15:30:17 +00:00
destination_zone_record_changeset = None
destination_zone_record_changeset = boto . route53 . record . ResourceRecordSets ( destination_connection , destination_zone_id )
2013-06-03 05:29:45 +00:00
2013-10-12 16:34:33 +00:00
logging . info ( ' Summary: ' )
logging . info ( ' Records migrated from source zone: {source_zone_name!s} to destination zone: {destination_zone_name!s} . '
. format ( source_zone_name = source_zone_name , destination_zone_name = destination_zone_name ) )
logging . info ( ' Record types selected for migration: {record_types_to_migrate!s} ). '
. format ( record_types_to_migrate = record_types_to_migrate ) )
logging . info ( ' Records examined: {examined_record_count!s} ). '
. format ( examined_record_count = examined_record_count ) )
logging . info ( ' Records migrated: {migrated_record_count!s} ). '
. format ( migrated_record_count = migrated_record_count ) )
logging . info ( ' Records not migrated because they exist in destination zone {destination_zone_name!s} : {existing_records_in_destination_zone_count!s} . '
. format ( destination_zone_name = destination_zone_name , existing_records_in_destination_zone_count = existing_records_in_destination_zone_count ) )
logging . info ( ' Records that exist in source zone {source_zone_name!s} and destination zone {destination_zone_name!s} and are identical: {identical_records_in_destination_zone_count!s} '
. format ( source_zone_name = source_zone_name , destination_zone_name = destination_zone_name , identical_records_in_destination_zone_count = identical_records_in_destination_zone_count ) )
logging . info ( ' Records that exist in source zone {source_zone_name!s} and destination zone {destination_zone_name!s} and are different: {different_records_in_destination_zone_count!s} '
. format ( source_zone_name = source_zone_name , destination_zone_name = destination_zone_name , different_records_in_destination_zone_count = different_records_in_destination_zone_count ) )