1#!/usr/bin/env python 2# Author: Chris Moyer 3# 4# route53 is similar to sdbadmin for Route53, it's a simple 5# console utility to perform the most frequent tasks with Route53 6# 7# Example usage. Use route53 get after each command to see how the 8# zone changes. 9# 10# Add a non-weighted record, change its value, then delete. Default TTL: 11# 12# route53 add_record ZPO9LGHZ43QB9 rr.example.com A 4.3.2.1 13# route53 change_record ZPO9LGHZ43QB9 rr.example.com A 9.8.7.6 14# route53 del_record ZPO9LGHZ43QB9 rr.example.com A 9.8.7.6 15# 16# Add a weighted record with two different weights. Note that the TTL 17# must be specified as route53 uses positional parameters rather than 18# option flags: 19# 20# route53 add_record ZPO9LGHZ43QB9 wrr.example.com A 1.2.3.4 600 foo9 10 21# route53 add_record ZPO9LGHZ43QB9 wrr.example.com A 4.3.2.1 600 foo8 10 22# 23# route53 change_record ZPO9LGHZ43QB9 wrr.example.com A 9.9.9.9 600 foo8 10 24# 25# route53 del_record ZPO9LGHZ43QB9 wrr.example.com A 1.2.3.4 600 foo9 10 26# route53 del_record ZPO9LGHZ43QB9 wrr.example.com A 9.9.9.9 600 foo8 10 27# 28# Add a non-weighted alias, change its value, then delete. Alaises inherit 29# their TTLs from the backing ELB: 30# 31# route53 add_alias ZPO9LGHZ43QB9 alias.example.com A Z3DZXE0Q79N41H lb-1218761514.us-east-1.elb.amazonaws.com. 32# route53 change_alias ZPO9LGHZ43QB9 alias.example.com. A Z3DZXE0Q79N41H lb2-1218761514.us-east-1.elb.amazonaws.com. 33# route53 delete_alias ZPO9LGHZ43QB9 alias.example.com. A Z3DZXE0Q79N41H lb2-1218761514.us-east-1.elb.amazonaws.com. 34 35def _print_zone_info(zoneinfo): 36 print "="*80 37 print "| ID: %s" % zoneinfo['Id'].split("/")[-1] 38 print "| Name: %s" % zoneinfo['Name'] 39 print "| Ref: %s" % zoneinfo['CallerReference'] 40 print "="*80 41 print zoneinfo['Config'] 42 print 43 44 45def create(conn, hostname, caller_reference=None, comment=''): 46 """Create a hosted zone, returning the nameservers""" 47 response = conn.create_hosted_zone(hostname, caller_reference, comment) 48 print "Pending, please add the following Name Servers:" 49 for ns in response.NameServers: 50 print "\t", ns 51 52def delete_zone(conn, hosted_zone_id): 53 """Delete a hosted zone by ID""" 54 response = conn.delete_hosted_zone(hosted_zone_id) 55 print response 56 57def ls(conn): 58 """List all hosted zones""" 59 response = conn.get_all_hosted_zones() 60 for zoneinfo in response['ListHostedZonesResponse']['HostedZones']: 61 _print_zone_info(zoneinfo) 62 63def get(conn, hosted_zone_id, type=None, name=None, maxitems=None): 64 """Get all the records for a single zone""" 65 response = conn.get_all_rrsets(hosted_zone_id, type, name, maxitems=maxitems) 66 # If a maximum number of items was set, we limit to that number 67 # by turning the response into an actual list (copying it) 68 # instead of allowing it to page 69 if maxitems: 70 response = response[:] 71 print '%-40s %-5s %-20s %s' % ("Name", "Type", "TTL", "Value(s)") 72 for record in response: 73 print '%-40s %-5s %-20s %s' % (record.name, record.type, record.ttl, record.to_print()) 74 75def _add_del(conn, hosted_zone_id, change, name, type, identifier, weight, values, ttl, comment): 76 from boto.route53.record import ResourceRecordSets 77 changes = ResourceRecordSets(conn, hosted_zone_id, comment) 78 change = changes.add_change(change, name, type, ttl, 79 identifier=identifier, weight=weight) 80 for value in values.split(','): 81 change.add_value(value) 82 print changes.commit() 83 84def _add_del_alias(conn, hosted_zone_id, change, name, type, identifier, weight, alias_hosted_zone_id, alias_dns_name, comment): 85 from boto.route53.record import ResourceRecordSets 86 changes = ResourceRecordSets(conn, hosted_zone_id, comment) 87 change = changes.add_change(change, name, type, 88 identifier=identifier, weight=weight) 89 change.set_alias(alias_hosted_zone_id, alias_dns_name) 90 print changes.commit() 91 92def add_record(conn, hosted_zone_id, name, type, values, ttl=600, 93 identifier=None, weight=None, comment=""): 94 """Add a new record to a zone. identifier and weight are optional.""" 95 _add_del(conn, hosted_zone_id, "CREATE", name, type, identifier, 96 weight, values, ttl, comment) 97 98def del_record(conn, hosted_zone_id, name, type, values, ttl=600, 99 identifier=None, weight=None, comment=""): 100 """Delete a record from a zone: name, type, ttl, identifier, and weight must match.""" 101 _add_del(conn, hosted_zone_id, "DELETE", name, type, identifier, 102 weight, values, ttl, comment) 103 104def add_alias(conn, hosted_zone_id, name, type, alias_hosted_zone_id, 105 alias_dns_name, identifier=None, weight=None, comment=""): 106 """Add a new alias to a zone. identifier and weight are optional.""" 107 _add_del_alias(conn, hosted_zone_id, "CREATE", name, type, identifier, 108 weight, alias_hosted_zone_id, alias_dns_name, comment) 109 110def del_alias(conn, hosted_zone_id, name, type, alias_hosted_zone_id, 111 alias_dns_name, identifier=None, weight=None, comment=""): 112 """Delete an alias from a zone: name, type, alias_hosted_zone_id, alias_dns_name, weight and identifier must match.""" 113 _add_del_alias(conn, hosted_zone_id, "DELETE", name, type, identifier, 114 weight, alias_hosted_zone_id, alias_dns_name, comment) 115 116def change_record(conn, hosted_zone_id, name, type, newvalues, ttl=600, 117 identifier=None, weight=None, comment=""): 118 """Delete and then add a record to a zone. identifier and weight are optional.""" 119 from boto.route53.record import ResourceRecordSets 120 changes = ResourceRecordSets(conn, hosted_zone_id, comment) 121 # Assume there are not more than 10 WRRs for a given (name, type) 122 responses = conn.get_all_rrsets(hosted_zone_id, type, name, maxitems=10) 123 for response in responses: 124 if response.name != name or response.type != type: 125 continue 126 if response.identifier != identifier or response.weight != weight: 127 continue 128 change1 = changes.add_change("DELETE", name, type, response.ttl, 129 identifier=response.identifier, 130 weight=response.weight) 131 for old_value in response.resource_records: 132 change1.add_value(old_value) 133 134 change2 = changes.add_change("UPSERT", name, type, ttl, 135 identifier=identifier, weight=weight) 136 for new_value in newvalues.split(','): 137 change2.add_value(new_value) 138 print changes.commit() 139 140def change_alias(conn, hosted_zone_id, name, type, new_alias_hosted_zone_id, new_alias_dns_name, identifier=None, weight=None, comment=""): 141 """Delete and then add an alias to a zone. identifier and weight are optional.""" 142 from boto.route53.record import ResourceRecordSets 143 changes = ResourceRecordSets(conn, hosted_zone_id, comment) 144 # Assume there are not more than 10 WRRs for a given (name, type) 145 responses = conn.get_all_rrsets(hosted_zone_id, type, name, maxitems=10) 146 for response in responses: 147 if response.name != name or response.type != type: 148 continue 149 if response.identifier != identifier or response.weight != weight: 150 continue 151 change1 = changes.add_change("DELETE", name, type, 152 identifier=response.identifier, 153 weight=response.weight) 154 change1.set_alias(response.alias_hosted_zone_id, response.alias_dns_name) 155 change2 = changes.add_change("UPSERT", name, type, identifier=identifier, weight=weight) 156 change2.set_alias(new_alias_hosted_zone_id, new_alias_dns_name) 157 print changes.commit() 158 159def help(conn, fnc=None): 160 """Prints this help message""" 161 import inspect 162 self = sys.modules['__main__'] 163 if fnc: 164 try: 165 cmd = getattr(self, fnc) 166 except: 167 cmd = None 168 if not inspect.isfunction(cmd): 169 print "No function named: %s found" % fnc 170 sys.exit(2) 171 (args, varargs, varkw, defaults) = inspect.getargspec(cmd) 172 print cmd.__doc__ 173 print "Usage: %s %s" % (fnc, " ".join([ "[%s]" % a for a in args[1:]])) 174 else: 175 print "Usage: route53 [command]" 176 for cname in dir(self): 177 if not cname.startswith("_"): 178 cmd = getattr(self, cname) 179 if inspect.isfunction(cmd): 180 doc = cmd.__doc__ 181 print "\t%-20s %s" % (cname, doc) 182 sys.exit(1) 183 184 185if __name__ == "__main__": 186 import boto 187 import sys 188 conn = boto.connect_route53() 189 self = sys.modules['__main__'] 190 if len(sys.argv) >= 2: 191 try: 192 cmd = getattr(self, sys.argv[1]) 193 except: 194 cmd = None 195 args = sys.argv[2:] 196 else: 197 cmd = help 198 args = [] 199 if not cmd: 200 cmd = help 201 try: 202 cmd(conn, *args) 203 except TypeError, e: 204 print e 205 help(conn, cmd.__name__) 206