-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathboto53.py
218 lines (176 loc) · 7.57 KB
/
boto53.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/env python
# NOTE FOR SETTING UP CREDENTIALS:
#
# Option #1
# Save creds in ~/.boto. Then set environment variable BOTO_CONFIG=~/.boto
#
# Option #2
# boto also will let it go in /etc/boto.cfg. This is how I use it for blueplanet sim machines.
#
# Example /etc/boto.cfg file
# [Credentials]
# aws_access_key_id = <<YOURS HERE>>
# aws_secret_access_key = <<YOURS HERE>>
from boto import connect_route53
from boto import route53
from boto.route53.record import ResourceRecordSets
from boto.route53.connection import Route53Connection
import boto.ec2
import sys
import os
import pprint
import logging
import re
from optparse import OptionParser, OptionGroup
# Credentials Option #3, for development.
# If you don't have a .boto config file, you can hard-code your creds here after
# enabling the two lines of code below.
#
# boto.config.set('Credentials', 'aws_access_key_id', 'YOURS GOES HERE')
# boto.config.set('Credentials', 'aws_secret_access_key', 'YOURS GOES HERE')
# route53 zone
zone_id = "YOURS HERE"
region = "us-east-1"
ttl = 300
def add_route53_record(name, ip_address=None):
'''
Pass in the name of the instance, as stored in the Name tag.
Sets the actual IP of the instance in route 53, unless you supply your own IP address.
'''
route53_ip = get_route53_ip(name)
actual_ip = get_instance_ip(name)
logging.info("Route53 IP=%s, Actual IP=%s" % (route53_ip, actual_ip))
if ip_address is not None: # override allowed
actual_ip = ip_address
if route53 is None and actual_ip is None:
logging.error("Invalid input supplied. HOST=%s, IP=%s " % (name, actual_ip))
return
if actual_ip is None:
logging.error("Could not find IP address for %s." % (name))
return
if actual_ip == route53_ip:
print "%s, IP=%s already exists." % (name, actual_ip)
else:
conn = connect_route53()
changes = ResourceRecordSets(conn, zone_id)
logging.info("DELETE %s, IP=%s, TTL=%s" % (name, route53_ip, ttl))
logging.info("CREATE %s IP=%s, TTL=%s" % (name, actual_ip, ttl))
if options.dryrun is True:
return
# Delete old record if it exists
if route53_ip is not None:
# NOTE: TTL is 300. But it must match in the record or this fails.
change1 = changes.add_change("DELETE", name, "A", ttl=ttl)
change1.add_value(route53_ip)
#create A record
change2 = changes.add_change("CREATE", name, "A", ttl=ttl)
change2.add_value(actual_ip)
result = changes.commit() # Note: If delete&create this commit does both in one transaction.
print "Updated Route53 %s, IP=%s" % (name, actual_ip)
return
def delete_route53_record(name):
'''
Deletes a record!
'''
# NOTICE: Our registered DNS aliases have a "pub-" prefix.
# Our instance names do not.
route53_ip = get_route53_ip(name)
logging.info("%s, IP=%s" % (name, route53_ip))
conn = connect_route53()
changes = ResourceRecordSets(conn, zone_id)
logging.info("DELETE %s, IP=%s, TTL=%s" % (name, route53_ip, ttl))
if options.dryrun is True:
return
# Delete old record if it exists
if route53_ip is not None:
# NOTE: TTL is 300. But it must match in the record or this fails.
change1 = changes.add_change("DELETE", name, "A", ttl=ttl)
change1.add_value(route53_ip)
result = changes.commit() # Note: If delete&create this commit does both in one transaction.
print "Delete %s, IP=%s" % (name, route53_ip)
else:
print "No record match found."
return
def get_localhost_ip():
'''
Only applies when running this tool directly on an EC2 instances -
Call amazon services to fetch the public IP of the local host.
'''
output = os.popen("curl -s http://169.254.169.254/latest/meta-data/public-ipv4").readlines()
if len(output) > 0:
pattern = re.compile(r'\d{1,3}\.\d{1,3}.\d{1,3}.\d{1,3}')
ip = output[0]
if pattern.match(ip): # Make sure this is an IP address
return ip
return None
def get_instance_ip(name):
'''
Return the IP of an instance that has a tag of "Name" that matches
'''
if options.local is True:
print "Getting public IP of localhost..."
return get_localhost_ip()
# Otherwise, let's go try to find the IP from EC2, based on the Name tag = hostname.
print "Searching EC2 region %s for %s..." % (region, name)
conn = boto.ec2.connect_to_region(region)
try:
reservations = conn.get_all_instances(filters={'tag:Name':name})
instance = reservations[0].instances[0]
return instance.ip_address
except:
return None
def get_route53_ip(name):
'''
Returns the IP addres registerd in route53. Note: This can differ from what the actual
IP address is if you don't use elastic. To get the actual IP address use get_instance_ip()
'''
conn = connect_route53()
record_lookup = {}
# Create a dict out of the data for easier lookup.
for record in conn.get_all_rrsets(zone_id):
record_lookup[record.name.rstrip('.')] = record.resource_records[0]
if name in record_lookup.keys():
return record_lookup[name]
else:
return None
if __name__ == '__main__':
parser = OptionParser()
parser.add_option("-d", "--delete", dest="delete_name",
help="Delete record.")
parser.add_option("-l", "--local", dest="local", action="store_true", default=False,
help="Add this option to --add when running directly on the EC2 instance for most reliable IP \
lookup.")
parser.add_option("-a", "--add", dest="add_name",
help="Add a record. It will attempt to lookup the IP address from aws. \
Can run this command from any host.")
parser.add_option("-i", "--ip",
dest="ip_address", default=None,
help="Force IP address to this value regardless of actual address")
parser.add_option("--dryrun", default=False, help="Shows what will happen. Does not execute.",
action="store_true", dest="dryrun")
parser.add_option("--log", default='ERROR', dest="loglevel",
help="Logging level. Use INFO for some info, and DEBUG for a lot.")
group1 = OptionGroup(parser, "EXAMPLE: Create record. The actual IP is retrieved \
by looking for an instance with the tag Name=<host>",
"./route53cli -n blueplanet9001.cyclone.cyaninc.com")
group2 = OptionGroup(parser, "EXAMPLE: Add -i <IP> to force create an entry regardless if host is running",
"./route53cli -n blueplanet9001.cyclone.cyaninc.com -i 54.10.0.3")
group3 = OptionGroup(parser, "EXAMPLE: Use --local option when running this command from the instance",
"./route53cli --local -a blueplanet9001.cyclone.cyaninc.com")
group4 = OptionGroup(parser, "EXAMPLE: Delete reccord",
"./route53cli --delete -n blueplanet9001.cyclone.cyaninc.com")
parser.add_option_group(group1)
parser.add_option_group(group2)
parser.add_option_group(group3)
parser.add_option_group(group4)
(options, args) = parser.parse_args()
if options.dryrun:
numeric_level = logging.INFO
else:
numeric_level = getattr(logging, options.loglevel.upper(), None)
logging.basicConfig(format='%(levelname)s:%(message)s', level=numeric_level)
if options.delete_name:
delete_route53_record(options.delete_name)
else:
add_route53_record(options.add_name, options.ip_address)
sys.exit(1)