2013-06-03 05:29:45 +00:00
#!/usr/bin/python
# Author: Colin Johnson / colin@cloudavail.com
2013-06-08 15:30:17 +00:00
# Date: 2013-06-08
2013-06-03 05:29:45 +00:00
# Version 0.1
# License Type: GNU GENERAL PUBLIC LICENSE, Version 3
#
import boto . route53 # import boto.route53 (not just boto) - need to import correct module
import ConfigParser # import ConfigParser - used for getting configuration
import re # import re used to find/replace zone
import sys # used to exit python program with exit code
import os # used to get app_name
2013-06-09 02:47:20 +00:00
import logging # used to write out log events - events that are neither required output nor error
import argparse # used to gather user input
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-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-08-01 00:12:25 +00:00
sys . stderr . write ( " An error occured when attempting to commit records to the zone \" " + destination_zone_name + " \" \n " )
sys . stderr . write ( " The error message given was: " + error . error_message + " . \n " )
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 ) :
compare_values = [ " type " , " ttl " , " resource_records " , " alias_hosted_zone_id " , " alias_dns_name " , " identifier " , " weight " , " region " ]
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-06-03 05:29:45 +00:00
app_name = os . path . basename ( __file__ )
2013-06-09 02:47:20 +00:00
parser = argparse . ArgumentParser ( )
parser . add_argument ( " --loglevel " , help = " set the log level when running route53-migrate-zone. " )
parser . add_argument ( " --config " , help = " choose the configuration file to be used when running route53-migrate-zone. " , default = ' config.ini ' )
args = parser . parse_args ( )
2013-06-13 16:36:11 +00:00
if args . loglevel is not None :
log_level = str . upper ( args . loglevel )
2013-06-09 02:47:20 +00:00
logging . basicConfig ( level = log_level )
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-06-04 20:27:07 +00:00
#functions: currently supports newzone - the functions are set automatically by the route53-migrate-zone script
functions = [ ]
2013-06-03 05:29:45 +00:00
2013-06-08 15:30:17 +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 " )
2013-06-03 05:29:45 +00:00
#
2013-06-08 15:30:17 +00:00
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-06-04 20:27:07 +00:00
record_types_to_migrate = [ " A " , " CNAME " , " MX " , " TXT " ]
2013-06-08 15:30:17 +00:00
if source_zone_name != destination_zone_name :
print app_name + " will rewrite domain names ending in \" " + source_zone_name + " \" to domain names ending in \" " + destination_zone_name + " \" "
2013-06-04 20:27:07 +00:00
functions . append ( " newzone " )
2013-06-03 05:29:45 +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-06-08 15:30:17 +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-06-08 15:30:17 +00:00
sys . stderr . write ( " An error occured when attempting to create a connection to AWS. \n " )
sys . stderr . write ( " The error message given was: " + error . error_message + " . \n " )
exit ( 69 )
2013-06-03 05:29:45 +00:00
2013-06-08 15:30:17 +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-06-08 15:30:17 +00:00
sys . stderr . write ( " An error occured when attempting to create a connection to AWS. \n " )
sys . stderr . write ( " The error message given was: " + error . error_message + " . \n " )
exit ( 69 )
2013-06-04 20:27:07 +00:00
2013-06-08 15:30:17 +00:00
#creates ResourceRecordSets object named source_zone_records (ResourceRecordSets = a collection of resource records)
source_zone_records = source_zone . get_records ( )
#creates ResourceRecordSets object named destination_zone_records (ResourceRecordSets = a collection of resource records)
destination_zone_records = destination_zone . get_records ( )
2013-06-04 20:27:07 +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-06-08 15:30:17 +00:00
#destination_zone_existing_resource_record_dict will be used to store all resource records that exist in destination zone
destination_zone_existing_resource_record_dict = { }
2013-06-04 20:27:07 +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
#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-06-08 15:30:17 +00:00
#get records from source_zone
for record in source_zone_records :
2013-06-03 05:29:45 +00:00
if record . type in record_types_to_migrate :
if " newzone " in functions :
2013-06-09 02:47:20 +00:00
destination_record = re . sub ( source_zone_name , destination_zone_name , record . name )
logging . info ( " Record \" " + record . name + " \" will be rewritten as \" " + destination_record + " \" . " )
record . name = destination_record
2013-06-08 15:30:17 +00:00
#test if record exists in destination_zone
if record . name in destination_zone_existing_resource_record_dict :
existing_records_in_destination_zone_count + = 1
#compare records in source_domain and destination_domain, store result as diff_result
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-06-09 02:47:20 +00:00
logging . info ( " Record \" " + record . name + " \" exists in source zone \" " + source_zone_name + " \" and destination zone \" " + destination_zone_name + " \" and is different. " )
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-06-09 02:47:20 +00:00
logging . info ( " Record \" " + record . name + " \" exists in source zone \" " + source_zone_name + " \" and destination zone \" " + destination_zone_name + " \" and is identical. " )
2013-06-04 20:27:07 +00:00
else :
2013-06-08 15:30:17 +00:00
sys . stderr . write ( " Diff of record " + record . name + " failed. \n " )
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-06-09 02:47:20 +00:00
logging . info ( " Record \" " + resource_record_dict [ record ] . name + " \" is an alias record set and will not be migrated. " + app_name + " does not currently support alias record sets. " )
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-06-09 02:47:20 +00:00
logging . debug ( " Uncommitted Record Count: " + str ( uncommitted_change_elements ) )
2013-06-03 05:29:45 +00:00
#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-06-09 02:47:20 +00:00
logging . info ( " Flushing Records: " + str ( 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
print " Summary: "
2013-06-08 15:30:17 +00:00
print " Records migrated from source zone: \" " + source_zone_name + " \" to destination zone: \" " + destination_zone_name + " \" . "
print " Record types selected for migration: " + str ( record_types_to_migrate ) + " . "
print " Records examined: " + str ( examined_record_count )
2013-06-04 20:27:07 +00:00
print " Records migrated: " + str ( migrated_record_count ) + " . "
2013-06-08 15:30:17 +00:00
print " Records not migrated because they exist in destination zone \" " + destination_zone_name + " \" : " + str ( existing_records_in_destination_zone_count )
print " Records that exist in source zone \" " + source_zone_name + " \" and destination zone \" " + destination_zone_name + " \" and are identical: " + str ( identical_records_in_destination_zone_count )
print " Records that exist in source zone \" " + source_zone_name + " \" and destination zone \" " + destination_zone_name + " \" and are different: " + str ( different_records_in_destination_zone_count )