-
Notifications
You must be signed in to change notification settings - Fork 0
/
config_region.py
executable file
·359 lines (242 loc) · 13 KB
/
config_region.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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
#!/bin/env python
import sys
import pprint
import boto3
from botocore.exceptions import ClientError
class config_region():
def __init__(self, region):
# We are hard-coding in the fact that our home region is us-west-2
# and that we have an AMI instance there named 'NAVO'.
#
# The name of the new region is the only input parameter.
#
# Later on below we will need a security key. Actually, just the
# public key, as that is all a new instance needs to know.
# Here, I have included that key in the code for now but should
# probably generate it in real time from a local private key
# file.
# There are situations where this script will fail, for instance
# when it times out waiting for the AMI to copy to the new
# region. To be safe and to allow for restarting, we will
# check for each element before trying to create it and skip
# any step where what we are trying to copy/create already
# exists.
pp = pprint.PrettyPrinter(indent=4)
uswest_client = boto3.client('ec2', region_name='us-west-2')
images = uswest_client.describe_images(Owners=['self'],
Filters=[{'Name': 'name', 'Values':['NAVO']}])
ami_original = images['Images'][0]['ImageId']
public_key = (b'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDJGGBlr5m7'
b'Tyd9iUYI9C17P74bP6UFEgMEHVmUnYQbmOLw7r6LuNJ3zbAc'
b'48/Fjv5nOFh2owZ8/XkL55BgSCPWbZTpaqbJ9zPrD59Dlz+y'
b'/1u37pNAzQQBNoIqNlSMSWUmjbq4aviTcBBNiZLdDz9zrClg'
b'g2dhoibrIeL5ZYPgHgqBksYF5VqsJgRcbVUJ8tholTtuQOxZ'
b'Desh7krimBdPDfEJk3NlH1lg2jPx93eIW63l4O1E5z404FWn'
b'ewUfNjI7sUkfspyjxwbPEPx7jS+mNxCxT3tL4gAlUWK4E9BM'
b's3PpJmVx7rn5FypE2cRK+m1Gekg5RantuvzJ6INedC5L')
#--------------------------------------------------------------
#--------------------------------------------------------------
# In our default region ('us-west-2' in eastern Oregon)
# we have a working EC2 instance called 'i-03ef4cacb7c6891a2'
# and have created an Amazon Machine Image (AMI) from it
# called 'ami-061fd1cf56be87d56' (with the name 'NAVO').
# AMIs can be created using boto3 but this is one area where
# one-off manual building make sense.
# Our instance runs on the 't2.micro' platform and consists
# of Amazon LINUX, Anaconda, and the NAVO servicemon package.
# We didn't add any disk; there was just enough space left on
# the default system disk to install the above and we our
# monitoring doesn't create any data files (summary information
# is sent to a server at IPAC in real time.
# In us-west-2, we also have a security keypair called 'NAVO'
# and the private key is kept securely on our IPAC machines
# as 'NAVO.pem'. This lets us SSH (or connect via boto3 or
# or AWS CLI) to the EC2 instance.
# The security group here was generated by the console setup
# wizard and allows any outbound traffic from the EC2 iestance
# (i.e., it can access anything) but only SSH/FTP on input.
# Note: we are going to use the same key pair we generated for
# us-west. Note that boto3 also has functions for creating
# new keypairs (ec2.create_key_pair()), which returns the new
# keypair private key for you to save away locally).
#--------------------------------------------------------------
#--------------------------------------------------------------
# AMI:
# Our main goal here is to copy the setup we have on us-west-2
# to a new region and fire up a clone of our EC2 instance there.
# To be concise, we aren't doing all the checks we should about
# whether stuff already exists in the new region, so be careful
# with this script.
# First, we have to copy our AMI and create our keypair over in
# the new region.
region_client = boto3.client ('ec2', region_name=region)
region_resource = boto3.resource('ec2', region_name=region)
try:
images = region_client.describe_images(Owners=['self'],
Filters=[{'Name': 'name', 'Values':['NAVO']}])
ami_id = images['Images'][0]['ImageId']
print('"NAVO" AMI already exists: ' + ami_id)
except:
response = region_client.copy_image(
Name='NAVO',
Description='Copied from us-west-2',
SourceImageId=ami_original,
SourceRegion='us-west-2'
)
ami_id = response['ImageId']
print('\nCopying AMI ' + ami_original + ' from us-west-2')
print(' to ' + region + ' AMI', ami_id, ' ...\n')
# Copying and AMI takes a little time and if we try to use it right
# away we will get an exception. The same thing is going to happen
# later when we 'start' an EC2 instance. In both cases the only
# thing we can do is poll until the resource is ready (in our case
# here the AMI will be 'pending' until then).
image = region_resource.Image(ami_id)
response = image.wait_until_exists(
Filters=[{'Name': 'state', 'Values': ['available']}])
print('AMI ID on ' + region + ' is now active.\n')
#--------------------------------------------------------------
# KEYPAIR:
# For the keypair, we actually only need to copy the public key
# (the private key, on our machine, is fine where it is). Curiously,
# when we let AWS generate the keys (which you can do through the
# console or via boto3 ec2.create_key_pair()), it returns the private
# key but not the public one.
# There are a couple of ways to get the public key. One is to start
# the ec2 instance on us-west-2, and get it from the .ssh directory.
# It is usually the only thing in the file "authorized_keys" but is
# definitely the one labeled with your name ('NAVO' in my case).
# The other way (on LINUX) is to generate the public key from the private
# key. I have my EC2 private key is a file named "NAVO.pem", so I can run
#
# ssh-keygen -y -f NAVO.pem
#
# and it will return the same public key string as above (minus the "NAVO"
# name at the end). I included my public key at the beginning of the
# code.
try:
keypairs = region_client.describe_key_pairs(
Filters=[{'Name': 'key-name', 'Values': ['NAVO']}])
keypair_id = keypairs['KeyPairs'][0]['KeyPairId']
print('"NAVO" key pair already exists: ' + keypair_id)
except:
response = region_client.import_key_pair(
KeyName='NAVO',
PublicKeyMaterial=public_key
)
print('Key pair "NAVO" imported.\n')
#--------------------------------------------------------------
# SECURITY GROUP (handlind network protocols):
# AWS takes a fairly simple approach to software firewalls (i.e. what
# network protocols will be allowed in on what ports). You define a
# "Security Group" (by which I guess they mean a 'group' of rules for
# protocol (e.g. 'tcp'), port (e.g. 22), and IP range ('0.0.0.0/0'
# means wide open). If you have a bunch of security groups defined,
# they are ORed together (i.e., any traffic that passes any of the
# rules in any of the security groups is allowed through).
# The default SG allows nothing coming in and everything going out.
# So you can SSH from the instance to somewhere else or access a
# remote HTTP web service but you can't SSH in.
# When you use the web page Console to create an instance, you usually
# get a second SG, labeled something like 'launch-wizard-1' which opens
# port 22 for inbound traffic (so you can SSH in) and replicates the
# wide open outbound rules for good measure.
# When we use boto3 calls to create an instance, however, it doesn't
# do this, leaving us with the default SG. So we need to open up port
# 22 (and 80 if we want to support a web server, 21 if we want TCP,
# and so on). Here we will create a 'NAVO' SG with just port 22.
try:
response = region_client.describe_security_groups(GroupNames=['NAVO'])
security_group_id = response['SecurityGroups'][0]['GroupId']
print('"NAVO" security group already exists: ' + security_group_id)
except:
response = region_client.describe_vpcs()
vpc_id = response.get('Vpcs', [{}])[0].get('VpcId', '')
try:
response = region_client.create_security_group(GroupName='NAVO',
Description='Basic SSH access.',
VpcId=vpc_id)
security_group_id = response['GroupId']
print('Security Group Created: %s in vpc: %s.\n'
% (security_group_id, vpc_id))
data = region_client.authorize_security_group_ingress(
GroupId=security_group_id,
IpPermissions=[
{'IpProtocol': 'tcp',
'FromPort': 22,
'ToPort': 22,
'IpRanges': [{'CidrIp': '0.0.0.0/0'}]}
])
print('and SSH TCP allowed access.\n')
except ClientError as e:
print('Error settinp SSH access.\n')
#--------------------------------------------------------------
# EC2 INSTANCE:
# Now we can create an instance using this AMI, keypair and
# security group:
# First check if an instance the the tag "NAVO" already exists
response = region_client.describe_instances(
Filters = [
{
'Name': 'tag:Name',
'Values': ['NAVO']
}
])
reservations = response['Reservations']
instance_id = ''
if len(reservations) > 0:
instances = reservations[0]['Instances']
for instance in instances:
instance_id = instance['InstanceId']
print('"NAVO" instance already exists: ' + instance_id)
if instance['State']['Name'] == 'terminated':
print('But that instance has been terminated.')
instance_id = ''
# If no instance (or instance terminated), create a new one
if instance_id == '':
instances = region_resource.create_instances(
ImageId=ami_id,
MinCount=1,
MaxCount=1,
InstanceType='t2.micro',
KeyName='NAVO'
)
instances[0].wait_until_running()
print('Instance created and running.\n')
instances[0].modify_attribute(Groups=[security_group_id])
print('SSH access set up.\n')
# With a Name tag of "NAVO" so we can find the instance later
instances[0].create_tags(Tags=[{'Key': 'Name', 'Value': 'NAVO'}])
print('Name:NAVO tag created.\n')
# And attach a role that lets us use SSM with this instance
# This IAM instance profile was created by the console when we
# created our original instance on us-west-2 and gave it SSM
# permissions. Such profiles are global, however, so we can
# invoke it here as well.
instance_id = instances[0].id
region_client.associate_iam_instance_profile(
IamInstanceProfile={
'Arn': 'arn:aws:iam::142510137673:instance-profile/AmazonSSMRoleForInstancesQuickSetup',
'Name': 'AmazonSSMRoleForInstancesQuickSetup'
},
InstanceId=instance_id
)
print('SSM IAM profile associated with instance.\n')
print('New "NAVO" instance ID: ', instance_id)
#--------------------------------------------------------------
# MAKE QUIESCENT:
# Now we will stop the instance, leaving the new region in a
# quiescent state.
try:
response = region_client.stop_instances(InstanceIds=[instance_id])
print('Instance stopped. Region ready but in quiescent state.')
except ClientError as e:
print('Error stopping region instance.\n')
def main():
if len(sys.argv) < 2:
print('Usage: config_region.py <region_name>\n')
sys.exit(0)
else:
region = config_region(sys.argv[1])
if __name__ == "__main__":
main()