aws-tools 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/aws_manager.rb +8 -2
- data/lib/aws_region.rb +756 -755
- metadata +38 -50
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: ca067a22cebe6f3a1fce85ce9feed048ff268a45
|
|
4
|
+
data.tar.gz: 1ec0ba2eb6779f228be4e08f47e4d3209afa00f9
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: e641830b7260a47d7eeaa45c265f7957f13f8a4d5538021cab4bec00b6c054c705e7513862ec793c30af2e25774fcf2e7497884718b82de4f8b70f91dd212d5e
|
|
7
|
+
data.tar.gz: 945cd6a46e045496e1924e1a4c0387564252422a85170a1b09cd7cddb2317c76bc7a0b71d6a5772d01d2505b08015228ae73ae3d9402892bf3ad5b209cd29d01
|
data/bin/aws_manager.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
-
require 'aws-sdk
|
|
3
|
+
require 'aws-sdk'
|
|
4
4
|
require 'yaml'
|
|
5
5
|
require 'optparse'
|
|
6
6
|
require 'erb'
|
|
@@ -96,6 +96,7 @@ def syntax
|
|
|
96
96
|
puts "Syntax:"
|
|
97
97
|
puts " EC2 Instance commands:"
|
|
98
98
|
puts " aws_manager.rb --region region run_instance <instance template file> "
|
|
99
|
+
puts " aws_manager.rb --region region [--mount volume:device] run_instance <instance template file> "
|
|
99
100
|
puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] [--choose first|oldest|newest] connect [id]"
|
|
100
101
|
puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] [--choose first|oldest|newest] start [id]"
|
|
101
102
|
puts " aws_manager.rb --region region [--environment environment] [--purpose purpose] [--choose first|oldest|newest] [--keep-one] stop [id]"
|
|
@@ -129,13 +130,14 @@ def syntax
|
|
|
129
130
|
end
|
|
130
131
|
def main
|
|
131
132
|
syntax if ARGV.length <= 0
|
|
132
|
-
params = ARGV.getopts("hr:e:p:fkc:", "choose:", "keep-one", "help", "region:", "environment:", "purpose:")
|
|
133
|
+
params = ARGV.getopts("hr:e:p:fkc:m:", "choose:", "mount:", "keep-one", "help", "region:", "environment:", "purpose:")
|
|
133
134
|
syntax if params['h'] or params['help']
|
|
134
135
|
purpose = params['p'] || params['purpose']
|
|
135
136
|
environment = params['e'] || params['environment']
|
|
136
137
|
region = params['r'] || params['region']
|
|
137
138
|
keep_one = params['k'] || params['keep-one']
|
|
138
139
|
selection_criteria = params['c'] || params['choose']
|
|
140
|
+
mount = params['m'] || params['mount']
|
|
139
141
|
selection_criteria = selection_criteria.downcase.to_sym if selection_criteria
|
|
140
142
|
syntax if selection_criteria and !([:first,:oldest, :newest].include?(selection_criteria))
|
|
141
143
|
syntax if ARGV.length <= 0
|
|
@@ -204,6 +206,10 @@ def main
|
|
|
204
206
|
end
|
|
205
207
|
image_options = YAML.load(ERB.new(File.read(instance_template)).result)
|
|
206
208
|
instance = region.create_instance(image_options)
|
|
209
|
+
if mount
|
|
210
|
+
(vol,dev) = mount.split(":")
|
|
211
|
+
instance.mount(vol, dev)
|
|
212
|
+
end
|
|
207
213
|
elsif command == 'terminate_instance'
|
|
208
214
|
if keep_one and filter_instances(region.find_instances({environment: environment, purpose: purpose }), ['running']).length < 2
|
|
209
215
|
puts "There are less than 2 instances, and keep_one flag set. Exiting."
|
data/lib/aws_region.rb
CHANGED
|
@@ -1,755 +1,756 @@
|
|
|
1
|
-
require 'aws-sdk
|
|
2
|
-
require 'yaml'
|
|
3
|
-
require 'json'
|
|
4
|
-
|
|
5
|
-
class AwsBase
|
|
6
|
-
def log(msg)
|
|
7
|
-
@logger.write("#{Time.now.strftime("%b %D, %Y %H:%S:%M:")} #{msg}\n") if @logger
|
|
8
|
-
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# @param
|
|
20
|
-
# @param
|
|
21
|
-
# @param
|
|
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
|
-
_buckets
|
|
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
|
-
# @param
|
|
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
|
-
# @param
|
|
208
|
-
# @
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
#
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
# @param
|
|
225
|
-
# @param
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
options[:
|
|
233
|
-
options[:
|
|
234
|
-
options[:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
#
|
|
242
|
-
# the
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
# * :
|
|
249
|
-
#
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
aws_path =
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
f
|
|
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
|
-
response
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
#
|
|
328
|
-
# @param
|
|
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
|
-
|
|
359
|
-
@
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
@
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
_tags
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
#
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
:
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
#
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
#
|
|
426
|
-
#
|
|
427
|
-
# * :
|
|
428
|
-
#
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
#
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
#
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
@
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
@
|
|
483
|
-
@
|
|
484
|
-
@
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
@
|
|
490
|
-
@
|
|
491
|
-
|
|
492
|
-
self.
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
@
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
#
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
response[
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
#
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
@region.
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
#
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
#
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
#
|
|
571
|
-
# @param
|
|
572
|
-
# @
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
#
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
@region.
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
if
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
mounts
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
if
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
#
|
|
680
|
-
# @
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
sg.
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
p.
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
#
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
#
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
#
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
:
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
#
|
|
738
|
-
# @
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
port =
|
|
742
|
-
|
|
743
|
-
raise "no
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
:
|
|
748
|
-
:
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
end
|
|
1
|
+
require 'aws-sdk'
|
|
2
|
+
require 'yaml'
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
class AwsBase
|
|
6
|
+
def log(msg)
|
|
7
|
+
@logger.write("#{Time.now.strftime("%b %D, %Y %H:%S:%M:")} #{msg}\n") if @logger
|
|
8
|
+
puts "#{Time.now.strftime("%b %D, %Y %H:%S:%M:")} #{msg}\n"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# AwsRegion is a simplified wrapper on top of a few of the Aws core objects
|
|
13
|
+
# The main goal is to expose a extremely simple interface for some our most
|
|
14
|
+
# frequently used Aws facilities.
|
|
15
|
+
class AwsRegion < AwsBase
|
|
16
|
+
attr_accessor :ec2, :region, :rds, :account_id, :elb, :cw, :s3, :sns
|
|
17
|
+
REGIONS = {'or' => "us-west-2", 'ca' => "us-west-1", 'va' => 'us-east-1'}
|
|
18
|
+
|
|
19
|
+
# @param region [String] must be one of the keys of the {AwsRegion::REGIONS REGIONS} static hash
|
|
20
|
+
# @param account_id [String] Aws account id
|
|
21
|
+
# @param access_key_id [String] Aws access key id
|
|
22
|
+
# @param secret_access_key [String] Aws secret access key
|
|
23
|
+
def initialize(region, account_id, access_key_id, secret_access_key, logger = nil)
|
|
24
|
+
@logger = logger
|
|
25
|
+
@region = REGIONS[region]
|
|
26
|
+
@account_id = account_id
|
|
27
|
+
Aws.config = {:access_key_id => access_key_id,
|
|
28
|
+
:secret_access_key => secret_access_key}
|
|
29
|
+
@ec2 = Aws::EC2::Client.new(region: @region)
|
|
30
|
+
@rds = Aws::RDS::Client.new(region: @region)
|
|
31
|
+
@elb = Aws::ElasticLoadBalancing::Client.new(region: @region)
|
|
32
|
+
@cw = Aws::CloudWatch::Client.new(region: @region)
|
|
33
|
+
@s3 = Aws::S3::Client.new(region: @region)
|
|
34
|
+
@sns = Aws::SNS::Client.new(region: @region)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Simple EC2 Instance finder. Can find using instance_id, or using
|
|
38
|
+
# :environment and :purpose instance tags which must both match.
|
|
39
|
+
#
|
|
40
|
+
# @param options [Hash] containing search criteria. Values can be:
|
|
41
|
+
# * :instance_id - identifies an exact instance
|
|
42
|
+
# * :environment - instance tag
|
|
43
|
+
# * :purpose - instance tag
|
|
44
|
+
# @return [Array<AwsInstance>] instances found to match criteria
|
|
45
|
+
def find_instances(options={})
|
|
46
|
+
instances = []
|
|
47
|
+
@ec2.describe_instances[:reservations].each do |i|
|
|
48
|
+
i.instances.each do |y|
|
|
49
|
+
instance = AwsInstance.new(self, {:instance => y})
|
|
50
|
+
if instance.state != 'terminated'
|
|
51
|
+
if options.has_key?(:environment) and options.has_key?(:purpose)
|
|
52
|
+
instances << instance if instance.tags[:environment] == options[:environment] and instance.tags[:purpose] == options[:purpose]
|
|
53
|
+
elsif options.has_key?(:instance_id)
|
|
54
|
+
instances << instance if instance.id == options[:instance_id]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
return instances
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Simple DB Instance finder. Can find using instance_id, or using
|
|
63
|
+
# :environment and :purpose instance tags which must both match.
|
|
64
|
+
#
|
|
65
|
+
# @param options [Hash] containing search criteria. Values can be:
|
|
66
|
+
# * :instance_id - identifies an exact instance
|
|
67
|
+
# * :environment - instance tag
|
|
68
|
+
# * :purpose - instance tag
|
|
69
|
+
# @return [Array<AwsDbInstance>] instances found to match criteria
|
|
70
|
+
def find_db_instances(options={})
|
|
71
|
+
instances = []
|
|
72
|
+
@rds.describe_db_instances[:db_instances].each do |i|
|
|
73
|
+
instance = AwsDbInstance.new(self, {:instance => i})
|
|
74
|
+
if options.has_key?(:instance_id)
|
|
75
|
+
instance.id == options[:instance_id]
|
|
76
|
+
instances << instance
|
|
77
|
+
elsif instance.tags[:environment] == options[:environment] and
|
|
78
|
+
instance.tags[:purpose] == options[:purpose]
|
|
79
|
+
instances << instance
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
instances
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Search region for a bucket by name
|
|
87
|
+
#
|
|
88
|
+
# @param options [Hash] containing search criteria. Values can be:
|
|
89
|
+
# * :bucket - Bucket name
|
|
90
|
+
# @return [Array<AwsBucket>] instances found to match criteria
|
|
91
|
+
def find_buckets(options={})
|
|
92
|
+
buckets = []
|
|
93
|
+
_buckets = @s3.list_buckets()
|
|
94
|
+
_buckets[:buckets].each do |b|
|
|
95
|
+
buckets << AwsBucket.new(self, {id: b[:name]}) if b[:name] == options[:bucket]
|
|
96
|
+
end
|
|
97
|
+
buckets
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Construct new EC2 instance
|
|
101
|
+
#
|
|
102
|
+
# @param options [Hash] containing initialization parameters. See {AwsInstance#initialize}
|
|
103
|
+
# @return [AwsInstance]
|
|
104
|
+
def create_instance(options={})
|
|
105
|
+
AwsInstance.new(self, options)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Construct new DB instance
|
|
109
|
+
#
|
|
110
|
+
# @param options [Hash] containing initialization parameters. See {AwsDbInstance#initialize}
|
|
111
|
+
# @return [AwsDbInstance]
|
|
112
|
+
def create_db_instance(options={})
|
|
113
|
+
AwsDbInstance.new(self, options)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Construct new CloudWatch instance
|
|
117
|
+
#
|
|
118
|
+
# @param options [Hash] containing initialization parameters. See {AwsCw#initialize}
|
|
119
|
+
# @return [AwsCw]
|
|
120
|
+
def create_cw_instance(options={})
|
|
121
|
+
AwsCw.new(self, options)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Construct new AwsBucket instance
|
|
125
|
+
#
|
|
126
|
+
# @param options [Hash] containing initialization parameters. See {AwsBucket#initialize}
|
|
127
|
+
# @return [AwsBucket]
|
|
128
|
+
def create_bucket(options={})
|
|
129
|
+
AwsBucket.new(self, options)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def create_sns_instance
|
|
133
|
+
AwsSns.new(self)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
class AwsSns
|
|
137
|
+
attr_accessor :region
|
|
138
|
+
def initialize(region)
|
|
139
|
+
@region = region
|
|
140
|
+
end
|
|
141
|
+
def publish(topic_arn, subject) #, message)
|
|
142
|
+
@region.sns.publish(topic_arn: topic_arn, message: "unused for texts", subject: subject)
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Methods for dealing with CloudWatch
|
|
147
|
+
class AwsCw < AwsBase
|
|
148
|
+
attr_accessor :region
|
|
149
|
+
|
|
150
|
+
# @param region [String] - Value from REGION static hash
|
|
151
|
+
def initialize(region, options={})
|
|
152
|
+
@region = region
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# Put a cw metric
|
|
156
|
+
# @param arg_csv [String] - CSV row: "namespace,name,value,dims"
|
|
157
|
+
# * Note that dims is formatted as an arbitrary semicolon separated list of name:value dimensions. For example:
|
|
158
|
+
# * "activeservers,count,10,env:prod;purp:test"
|
|
159
|
+
# @return [Aws::PageableResponse]
|
|
160
|
+
def put_metric(arg_csv)
|
|
161
|
+
(namespace, name, value, dims) = arg_csv.split(",")
|
|
162
|
+
dimensions = []
|
|
163
|
+
dims.split(";").each do |d|
|
|
164
|
+
(n, v) = d.split(":")
|
|
165
|
+
dimensions << {:name => n, :value => v}
|
|
166
|
+
end
|
|
167
|
+
args = {:namespace => namespace}
|
|
168
|
+
metric ={:metric_name => name, :value => value.to_f, :timestamp => Time.now, :dimensions => dimensions}
|
|
169
|
+
args[:metric_data] = [metric]
|
|
170
|
+
@region.cw.put_metric_data(args)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Methods for dealing with S3 buckets
|
|
175
|
+
class AwsBucket < AwsBase
|
|
176
|
+
attr_accessor :region
|
|
177
|
+
|
|
178
|
+
# Constructs a bucket instance from an existing bucket, or creates a new one with the name
|
|
179
|
+
# @param region [String] - Value from REGION static hash
|
|
180
|
+
# @param options [Hash] - Possible values:
|
|
181
|
+
# * :id - id of existing bucket
|
|
182
|
+
# * :bucket - Name of bucket to find or create
|
|
183
|
+
def initialize(region, options={})
|
|
184
|
+
@region = region
|
|
185
|
+
if options.has_key?(:id)
|
|
186
|
+
@id = options[:id]
|
|
187
|
+
elsif options.has_key?(:bucket)
|
|
188
|
+
bucket = options[:bucket]
|
|
189
|
+
if @region.find_buckets({bucket: bucket}).length <= 0
|
|
190
|
+
@region.s3.create_bucket({:bucket => bucket,
|
|
191
|
+
:create_bucket_configuration => {:location_constraint => @region.region}})
|
|
192
|
+
if @region.find_buckets({bucket: bucket}).length <= 0
|
|
193
|
+
raise "Error creating bucket: #{bucket} in region: #{@region.region}"
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
@id = bucket
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Delete this bucket instance
|
|
201
|
+
# @return [AwsPageableResponse]]
|
|
202
|
+
def delete
|
|
203
|
+
@region.s3.delete_bucket({bucket: @id})
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Put a local file to this bucket
|
|
207
|
+
# @param filename [String] - local file name
|
|
208
|
+
# @param file_identity [String] - S3 file path
|
|
209
|
+
# @return [AwsPageableResponse]
|
|
210
|
+
def put_file(filename, file_identity)
|
|
211
|
+
File.open(filename, 'r') do |reading_file|
|
|
212
|
+
resp = @region.s3.put_object(
|
|
213
|
+
acl: "bucket-owner-full-control",
|
|
214
|
+
body: reading_file,
|
|
215
|
+
bucket: @id,
|
|
216
|
+
key: file_identity
|
|
217
|
+
)
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# puts a local file to an s3 object in bucket on path
|
|
222
|
+
# example: put_local_file(:bucket=>"bucket", :local_file_path=>"/tmp/bar/foo.txt", :aws_path=>"b")
|
|
223
|
+
# would make an s3 object named foo.txt in bucket/b
|
|
224
|
+
# @param local_file_path [String] - Location of file to put
|
|
225
|
+
# @param aws_path [String] - S3 path to put the file
|
|
226
|
+
# @param options [Hash] - Can contain any valid S3 bucket options see [docs](http://docs.aws.amazon.com/sdkforruby/api/frames.html)
|
|
227
|
+
def put(local_file_path, aws_path, options={})
|
|
228
|
+
aws_path = aws_path[0..-2] if aws_path[-1..-1] == '/'
|
|
229
|
+
s3_path = "#{aws_path}/#{File.basename(local_file_path)}"
|
|
230
|
+
log "s3 writing #{local_file_path} to bucket #{@id} path: #{aws_path} s3 path: #{s3_path}"
|
|
231
|
+
f = File.open local_file_path, 'rb'
|
|
232
|
+
options[:bucket] = @id
|
|
233
|
+
options[:key] = s3_path
|
|
234
|
+
options[:body] = f
|
|
235
|
+
options[:storage_class] = 'REDUCED_REDUNDANCY'
|
|
236
|
+
result = @region.s3.put_object(params=options)
|
|
237
|
+
f.close
|
|
238
|
+
result
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# prefix is something like: hchd-A-A-Items
|
|
242
|
+
# This will return in an array of strings the names of all objects in s3 in
|
|
243
|
+
# the :aws_path under :bucket starting with passed-in prefix
|
|
244
|
+
# example: :aws_path=>'development', :prefix=>'broadhead'
|
|
245
|
+
# would return array of names of objects in said bucket
|
|
246
|
+
# matching (in regex terms) development/broadhead.*
|
|
247
|
+
# @param options [Hash] - Can contain:
|
|
248
|
+
# * :aws_path - first part of S3 path to search
|
|
249
|
+
# * :prefix - Actually suffix of path to search.
|
|
250
|
+
# @return [Array<Hash>] - 0 or more objects
|
|
251
|
+
def find(options={})
|
|
252
|
+
aws_path = options[:aws_path]
|
|
253
|
+
prefix = options[:prefix]
|
|
254
|
+
aws_path = '' if aws_path.nil?
|
|
255
|
+
aws_path = aws_path[0..-2] if aws_path[-1..-1] == '/'
|
|
256
|
+
log "s3 searching bucket:#{@id} for #{aws_path}/#{prefix}"
|
|
257
|
+
objects = @region.s3.list_objects(:bucket => @id,
|
|
258
|
+
:prefix => "#{aws_path}/#{prefix}")
|
|
259
|
+
f = objects.contents.collect(&:key)
|
|
260
|
+
log "s3 searched got: #{f.inspect}"
|
|
261
|
+
f
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# writes contents of S3 object to local file
|
|
265
|
+
# example: get( :s3_path_to_object=>development/myfile.txt',
|
|
266
|
+
# :dest_file_path=>'/tmp/foo.txt')
|
|
267
|
+
# would write to local /tmp/foo.txt a file retrieved from s3
|
|
268
|
+
# at development/myfile.txt
|
|
269
|
+
# @param options [Hash] - Can contain:
|
|
270
|
+
# * :s3_path_to_object - S3 object path
|
|
271
|
+
# * :dest_file_path - local file were file will be written
|
|
272
|
+
# @return [Boolean]
|
|
273
|
+
def get(options={})
|
|
274
|
+
begin
|
|
275
|
+
s3_path_to_object = options[:s3_path_to_object]
|
|
276
|
+
dest_file_path = options[:dest_file_path]
|
|
277
|
+
File.delete dest_file_path if File.exists?(dest_file_path)
|
|
278
|
+
log "s3 get bucket:#{@id} path:#{s3_path_to_object} dest:#{dest_file_path}"
|
|
279
|
+
response = @region.s3.get_object(:bucket => @id,
|
|
280
|
+
:key => s3_path_to_object)
|
|
281
|
+
response.body.rewind
|
|
282
|
+
File.open(dest_file_path, 'wb') do |file|
|
|
283
|
+
response.body.each { |chunk| file.write chunk }
|
|
284
|
+
end
|
|
285
|
+
rescue Exception => e
|
|
286
|
+
return false
|
|
287
|
+
end
|
|
288
|
+
true
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# deletes from s3 an object at :s3_path_to_object
|
|
292
|
+
# @param options [Hash] - Can be:
|
|
293
|
+
# * :s3_path_to_object
|
|
294
|
+
# @return [Boolean]
|
|
295
|
+
def delete_object(options={})
|
|
296
|
+
begin
|
|
297
|
+
s3_path_to_object = options[:s3_path_to_object]
|
|
298
|
+
log "s3 delete #{s3_path_to_object}"
|
|
299
|
+
@region.s3.delete_object(:bucket => @id,
|
|
300
|
+
:key => s3_path_to_object)
|
|
301
|
+
rescue Exception => e
|
|
302
|
+
return false
|
|
303
|
+
end
|
|
304
|
+
true
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# delete all objects in a bucket
|
|
308
|
+
# @return [Boolean]
|
|
309
|
+
def delete_all_objects
|
|
310
|
+
begin
|
|
311
|
+
response = @region.s3.list_objects({:bucket => @id})
|
|
312
|
+
response[:contents].each do |obj|
|
|
313
|
+
@region.s3.delete_object(:bucket => @id,
|
|
314
|
+
:key => obj[:key])
|
|
315
|
+
end
|
|
316
|
+
rescue Exception => e
|
|
317
|
+
return false
|
|
318
|
+
end
|
|
319
|
+
true
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Class to handle RDS Db instances
|
|
324
|
+
class AwsDbInstance < AwsBase
|
|
325
|
+
attr_accessor :id, :tags, :region, :endpoint
|
|
326
|
+
|
|
327
|
+
# Creates an AwsDbInstance for an existing instance or creates a new database
|
|
328
|
+
# @param region [String] - - Value from REGION static hash
|
|
329
|
+
# @param options [Hash] - Can contain:
|
|
330
|
+
# * :instance - If specified, create an instance of this class using this RDS instance.
|
|
331
|
+
# * :opts - [Hash] - Includes parameters for constructing the database. The format is:
|
|
332
|
+
# * :db_instance_identifier - RDS instance identifier
|
|
333
|
+
# * :db_subnet_group_name - DB Subgroup name
|
|
334
|
+
# * :publicly_accessible - [true|false]
|
|
335
|
+
# * :db_instance_class - RDS db instance class
|
|
336
|
+
# * :availability_zone - RDS/Aws availability zone
|
|
337
|
+
# * :multi_az - [true|false]
|
|
338
|
+
# * :engine - RDS engine (Only tested with Mysql at this point)
|
|
339
|
+
# * :tags - Tags to be applied to RDS instance. The follow are required. Arbitrary tags may also be added.
|
|
340
|
+
# * :environment - Environment designation - can be anything. Used to locate instance with other aws-tools
|
|
341
|
+
# * :purpose - Purpose designation - can be anything. Used to locate instance with other aws-tools
|
|
342
|
+
# * :name - Name will appear in the Aws web page if you set this
|
|
343
|
+
# * :snapshot_name - Name of the snapshot that will be used to construct the new instance. This name will be matched with the RDS db_instance_identifier. The latest snapshot will be used.
|
|
344
|
+
# * :vpc_security_group_ids: - Comma separated list of security groups that will be applied to this instance
|
|
345
|
+
def initialize(region, options = {})
|
|
346
|
+
@region = region
|
|
347
|
+
opts = options[:opts]
|
|
348
|
+
if !options.has_key?(:instance)
|
|
349
|
+
@id = opts[:db_instance_identifier]
|
|
350
|
+
snapshot_name = options[:snapshot_name]
|
|
351
|
+
if 0 < @region.find_db_instances({:instance_id => @id}).length
|
|
352
|
+
log "Error, instance: #{@id} already exists"
|
|
353
|
+
return
|
|
354
|
+
end
|
|
355
|
+
last = self.get_latest_db_snapshot({:snapshot_name => snapshot_name})
|
|
356
|
+
log "Restoring: #{last.db_instance_identifier}, snapshot: #{last.db_instance_identifier} from : #{last.snapshot_create_time}"
|
|
357
|
+
opts[:db_snapshot_identifier] = last.db_snapshot_identifier
|
|
358
|
+
response = @region.rds.restore_db_instance_from_db_snapshot(opts)
|
|
359
|
+
@_instance = response[:db_instance]
|
|
360
|
+
@region.rds.add_tags_to_resource({:resource_name => "arn:aws:rds:#{@region.region}:#{@region.account_id}:db:#{@id}",
|
|
361
|
+
:tags => [{:key => "environment", :value => options[:environment]},
|
|
362
|
+
{:key => "purpose", :value => options[:purpose]}]})
|
|
363
|
+
|
|
364
|
+
self.wait
|
|
365
|
+
|
|
366
|
+
opts = {:db_instance_identifier => @id,
|
|
367
|
+
:vpc_security_group_ids => options[:vpc_security_group_ids]}
|
|
368
|
+
@region.rds.modify_db_instance(opts)
|
|
369
|
+
else
|
|
370
|
+
@_instance = options[:instance]
|
|
371
|
+
@id = @_instance[:db_instance_identifier]
|
|
372
|
+
end
|
|
373
|
+
@tags = {}
|
|
374
|
+
_tags = @region.rds.list_tags_for_resource({:resource_name => "arn:aws:rds:#{@region.region}:#{@region.account_id}:db:#{@id}"})
|
|
375
|
+
_tags[:tag_list].each do |t|
|
|
376
|
+
@tags[t[:key].to_sym] = t[:value]
|
|
377
|
+
end
|
|
378
|
+
@endpoint = @_instance.endpoint[:address]
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Delete a database and be sure to capture a final stapshot
|
|
382
|
+
# @return [Boolean] - A return value of true only means that the command was issued. The caller should follow up later with a call to determine status in order to know when the delete has been completed
|
|
383
|
+
def delete(options={})
|
|
384
|
+
log "Deleting database: #{@id}"
|
|
385
|
+
opts = {:db_instance_identifier => @id,
|
|
386
|
+
:skip_final_snapshot => false,
|
|
387
|
+
:final_db_snapshot_identifier => "#{@id}-#{Time.now.strftime("%Y-%m-%d-%H-%M-%S")}"}
|
|
388
|
+
begin
|
|
389
|
+
i = @region.rds.delete_db_instance(opts)
|
|
390
|
+
rescue Exception => e
|
|
391
|
+
return false
|
|
392
|
+
end
|
|
393
|
+
true
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# Purge db snapshots, keep just one - the latest
|
|
397
|
+
# @return [Boolean]
|
|
398
|
+
def purge_db_snapshots
|
|
399
|
+
latest = 0
|
|
400
|
+
@region.rds.describe_db_snapshots[:db_snapshots].each do |i|
|
|
401
|
+
if i.snapshot_type == "manual" and i.db_instance_identifier == @id
|
|
402
|
+
if i.snapshot_create_time.to_i > latest
|
|
403
|
+
latest = i.snapshot_create_time.to_i
|
|
404
|
+
end
|
|
405
|
+
end
|
|
406
|
+
end
|
|
407
|
+
@region.rds.describe_db_snapshots[:db_snapshots].each do |i|
|
|
408
|
+
if i.snapshot_type == "manual" and i.db_instance_identifier == @id
|
|
409
|
+
if i.snapshot_create_time.to_i != latest
|
|
410
|
+
log "Removing snapshot: #{i.db_snapshot_identifier}/#{i.snapshot_create_time.to_s}"
|
|
411
|
+
begin
|
|
412
|
+
@region.rds.delete_db_snapshot({:db_snapshot_identifier => i.db_snapshot_identifier})
|
|
413
|
+
rescue
|
|
414
|
+
log "Error removing snapshot: #{i.db_snapshot_identifier}/#{i.snapshot_create_time.to_s}"
|
|
415
|
+
return false
|
|
416
|
+
end
|
|
417
|
+
else
|
|
418
|
+
log "Keeping snapshot: #{i.db_snapshot_identifier}/#{i.snapshot_create_time.to_s}"
|
|
419
|
+
end
|
|
420
|
+
end
|
|
421
|
+
end
|
|
422
|
+
true
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
# Wait for the database to get to a state - we are usually waiting for it to be "available"
|
|
426
|
+
# @param options [Hash] - Can be:
|
|
427
|
+
# * :desired_status - Default: "available" - The RDS Status that is sought
|
|
428
|
+
# * :timeout - Default: 600 seconds. - The time to wait for the status before returning failure
|
|
429
|
+
# @return [Boolean]
|
|
430
|
+
def wait(options = {:desired_status => "available",
|
|
431
|
+
:timeout => 600})
|
|
432
|
+
inst = @region.find_db_instances({:instance_id => @id})[0]
|
|
433
|
+
if !inst
|
|
434
|
+
log "Error, instance: #{@id} not found"
|
|
435
|
+
return false
|
|
436
|
+
end
|
|
437
|
+
t0 = Time.now.to_i
|
|
438
|
+
while inst.status != options[:desired_status]
|
|
439
|
+
inst = @region.find_db_instances({:instance_id => @id})[0]
|
|
440
|
+
log "Database: #{@id} at #{@endpoint}. Current status: #{inst.status}"
|
|
441
|
+
if Time.now.to_i - t0 > options[:timeout]
|
|
442
|
+
log "Timed out waiting for database: #{@id} at #{@endpoint} to move into status: #{options[:desired_status]}. Current status: #{inst.status}"
|
|
443
|
+
return false
|
|
444
|
+
end
|
|
445
|
+
sleep 20
|
|
446
|
+
end
|
|
447
|
+
return true
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# Get the status of a database
|
|
451
|
+
# @return [String] - Current status of this database
|
|
452
|
+
def status
|
|
453
|
+
@_instance.db_instance_status
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# Get the name of the latest snapshot
|
|
457
|
+
# @return [Hash] - Hash describing RDS Snapshot. See [RDS Tech Docs](http://docs.aws.amazon.com/sdkforruby/api/Aws/RDS/V20130909.html)
|
|
458
|
+
def get_latest_db_snapshot(options={})
|
|
459
|
+
snapshot_name = options.has_key?(:snapshot_name) ? options[:snapshot_name] : @id
|
|
460
|
+
|
|
461
|
+
last = nil
|
|
462
|
+
last_t = 0
|
|
463
|
+
@region.rds.describe_db_snapshots[:db_snapshots].each do |i|
|
|
464
|
+
if i.db_instance_identifier == snapshot_name and (last.nil? or i.snapshot_create_time > last_t)
|
|
465
|
+
last = i
|
|
466
|
+
last_t = i.snapshot_create_time
|
|
467
|
+
end
|
|
468
|
+
end
|
|
469
|
+
last
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
# Class to handle EC2 Instances
|
|
475
|
+
class AwsInstance < AwsBase
|
|
476
|
+
attr_accessor :id, :tags, :region, :private_ip, :public_ip, :_instance
|
|
477
|
+
|
|
478
|
+
def initialize(region, options = {})
|
|
479
|
+
@tags = {}
|
|
480
|
+
@region = region
|
|
481
|
+
if options.has_key?(:instance)
|
|
482
|
+
@_instance = options[:instance]
|
|
483
|
+
@id = @_instance[:instance_id]
|
|
484
|
+
@public_ip = @_instance[:public_ip_address]
|
|
485
|
+
@private_ip = @_instance[:private_ip_address]
|
|
486
|
+
else
|
|
487
|
+
resp = @region.ec2.run_instances(options[:template])
|
|
488
|
+
raise "Error creating instance using options" if resp.nil? or resp[:instances].length <= 0
|
|
489
|
+
@_instance = resp[:instances][0]
|
|
490
|
+
@id = @_instance[:instance_id]
|
|
491
|
+
@tags = options[:tags]
|
|
492
|
+
self.add_tags(@tags)
|
|
493
|
+
self.wait
|
|
494
|
+
instance = @region.ec2.describe_instances(:instance_ids => [@id])[0][0].instances[0]
|
|
495
|
+
@public_ip = instance[:public_ip_address]
|
|
496
|
+
@private_ip = instance[:private_ip_address]
|
|
497
|
+
raise "could not get ip address" if @public_ip.nil? && @private_ip.nil?
|
|
498
|
+
self.inject_into_environment
|
|
499
|
+
end
|
|
500
|
+
@_instance.tags.each do |t|
|
|
501
|
+
@tags[t[:key].to_sym] = t[:value]
|
|
502
|
+
end
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
# Determine the state of an ec2 instance
|
|
507
|
+
# @param use_cached_state [Boolean] - When true will use a cached version of the state rather than querying EC2 directly
|
|
508
|
+
def state(use_cached_state=true)
|
|
509
|
+
if !use_cached_state
|
|
510
|
+
response = @region.ec2.describe_instances({instance_ids: [@id]})
|
|
511
|
+
response[:reservations].each do |res|
|
|
512
|
+
res[:instances].each do |inst|
|
|
513
|
+
if inst[:instance_id] == @id
|
|
514
|
+
return inst[:state][:name].strip()
|
|
515
|
+
end
|
|
516
|
+
end
|
|
517
|
+
end
|
|
518
|
+
return ""
|
|
519
|
+
else
|
|
520
|
+
@_instance.state[:name].strip()
|
|
521
|
+
end
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
# Start an EC2 instance
|
|
525
|
+
# @param wait [Boolean] - When true, will wait for instance to move into "running" state before returning
|
|
526
|
+
def start(wait=false)
|
|
527
|
+
if self.state(use_cached_state = false) != "stopped"
|
|
528
|
+
log "Instance cannot be started - #{@region.region}://#{@id} is in the state: #{self.state}"
|
|
529
|
+
return
|
|
530
|
+
end
|
|
531
|
+
log "Starting instance: #{@region.region}://#{@id}"
|
|
532
|
+
@region.ec2.start_instances({:instance_ids => [@id]})
|
|
533
|
+
if wait
|
|
534
|
+
begin
|
|
535
|
+
sleep 10
|
|
536
|
+
log "Starting instance: #{@region.region}://#{@id} - state: #{self.state}"
|
|
537
|
+
end while self.state(use_cached_state = false) != "running"
|
|
538
|
+
end
|
|
539
|
+
if @tags.has_key?("elastic_ip")
|
|
540
|
+
@region.ec2.associate_address({:instance_id => @id, :public_ip => @tags['elastic_ip']})
|
|
541
|
+
log "Associated ip: #{@tags['elastic_ip']} with instance: #{@id}"
|
|
542
|
+
elsif @tags.has_key?("elastic_ip_allocation_id")
|
|
543
|
+
@region.ec2.associate_address({:instance_id => @id, :allocation_id => @tags['elastic_ip_allocation_id']})
|
|
544
|
+
log "Associated allocation id: #{@tags['elastic_ip_allocation_id']} with instance: #{@id}"
|
|
545
|
+
end
|
|
546
|
+
if @tags.has_key?("elastic_lb")
|
|
547
|
+
self.add_to_lb(@tags["elastic_lb"])
|
|
548
|
+
log "Adding instance: #{@id} to '#{@tags['elastic_lb']}' load balancer"
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
# Add tags to an instance
|
|
553
|
+
# @param h_tags [Hash] - Hash of tags to add to instance
|
|
554
|
+
def add_tags(h_tags)
|
|
555
|
+
tags = []
|
|
556
|
+
h_tags.each do |k, v|
|
|
557
|
+
tags << {:key => k.to_s, :value => v}
|
|
558
|
+
end
|
|
559
|
+
resp = @region.ec2.create_tags({:resources => [@id],
|
|
560
|
+
:tags => tags})
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
# Add an instance to an elastic lb
|
|
564
|
+
# @param lb_name [String] - Name of elastic load balancer
|
|
565
|
+
def add_to_lb(lb_name)
|
|
566
|
+
@region.elb.register_instances_with_load_balancer({:load_balancer_name => lb_name,
|
|
567
|
+
:instances => [{:instance_id => @id}]})
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
# Remove instance from elastic lb
|
|
571
|
+
# @param instance [AwsInstance] Instance to remove from lb
|
|
572
|
+
# @param lb_name [String] Lb name from which the instance is to be removed
|
|
573
|
+
# @return [Aws::PageableResponse]
|
|
574
|
+
def remove_from_lb(lb_name)
|
|
575
|
+
lb = @region.elb.describe_load_balancers({:load_balancer_names => [lb_name]})
|
|
576
|
+
if lb and lb[:load_balancer_descriptions].length > 0
|
|
577
|
+
lb[:load_balancer_descriptions][0][:instances].each do |lb_i|
|
|
578
|
+
if lb_i[:instance_id] == @id
|
|
579
|
+
@elb.deregister_instances_from_load_balancer({:load_balancer_name => lb_name,
|
|
580
|
+
:instances => [{:instance_id => @id}]})
|
|
581
|
+
sleep 30
|
|
582
|
+
end
|
|
583
|
+
end
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
# Terminates ec2 instance
|
|
588
|
+
def terminate()
|
|
589
|
+
eject_from_environment
|
|
590
|
+
@region.ec2.terminate_instances({:instance_ids => [@id]})
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
# Stops an ec2 instance
|
|
594
|
+
# @param wait [Boolean] - When true, will wait for the instance to be completely stopped before returning
|
|
595
|
+
def stop(wait=false)
|
|
596
|
+
if self.state(use_cached_state = false) != "running"
|
|
597
|
+
log "Instance cannot be stopped - #{@region.region}://#{@id} is in the state: #{self.state}"
|
|
598
|
+
return
|
|
599
|
+
end
|
|
600
|
+
self.eject_from_environment
|
|
601
|
+
if @tags.has_key?("elastic_lb")
|
|
602
|
+
log "Removing instance: #{@id} from '#{@tags['elastic_lb']}' load balancer"
|
|
603
|
+
remove_from_lb(tags["elastic_lb"])
|
|
604
|
+
end
|
|
605
|
+
log "Stopping instance: #{@region.region}://#{@id}"
|
|
606
|
+
@region.ec2.stop_instances({:instance_ids => [@id]})
|
|
607
|
+
while self.state(use_cached_state = false) != "stopped"
|
|
608
|
+
sleep 10
|
|
609
|
+
log "Stopping instance: #{@region.region}://#{@id} - state: #{self.state}"
|
|
610
|
+
end if wait
|
|
611
|
+
if self.state(use_cached_state = false) == "stopped"
|
|
612
|
+
log "Instance stopped: #{@region.region}://#{@id}"
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
# Connects using ssh to an ec2 instance
|
|
617
|
+
def connect
|
|
618
|
+
if self.state(use_cached_state = false) != "running"
|
|
619
|
+
log "Cannot connect, instance: #{@region.region}://#{@id} due to its state: #{self.state}"
|
|
620
|
+
return
|
|
621
|
+
end
|
|
622
|
+
ip = (self.public_ip && self.public_ip.strip() != "") ? self.public_ip : self.private_ip
|
|
623
|
+
log "Connecting: ssh -i ~/.ssh/aws/#{@tags[:key]} #{@tags[:user]}@#{ip}"
|
|
624
|
+
exec "ssh -i ~/.ssh/aws/#{@tags[:key]} #{@tags[:user]}@#{ip}"
|
|
625
|
+
end
|
|
626
|
+
def eject_from_environment
|
|
627
|
+
if @tags.has_key?(:elastic_lb)
|
|
628
|
+
log "Removing instance: #{@id} from '#{@tags[:elastic_lb]}' load balancer"
|
|
629
|
+
self.remove_from_lb(tags[:elastic_lb])
|
|
630
|
+
end
|
|
631
|
+
if @tags.has_key?(:security_groups_foreign)
|
|
632
|
+
self.revoke_sg_ingress(@tags[:security_groups_foreign].split(","))
|
|
633
|
+
end
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
def inject_into_environment
|
|
637
|
+
if @tags.has_key?(:elastic_ip)
|
|
638
|
+
@region.ec2.associate_address({:instance_id => @id, :public_ip => @tags[:elastic_ip]})
|
|
639
|
+
log "Associated ip: #{@tags[:elastic_ip]} with instance: #{@id}"
|
|
640
|
+
elsif @tags.has_key?(:elastic_ip_allocation_id)
|
|
641
|
+
@region.ec2.associate_address({:instance_id => @id, :allocation_id => @tags[:elastic_ip_allocation_id]})
|
|
642
|
+
log "Associated allocation id: #{@tags[:elastic_ip_allocation_id]} with instance: #{@id}"
|
|
643
|
+
end
|
|
644
|
+
if @tags.has_key?(:mount_points)
|
|
645
|
+
mounts = @tags[:mount_points].split(";")
|
|
646
|
+
mounts.each do |mnt|
|
|
647
|
+
(volume_id,device) = mnt.split(",")
|
|
648
|
+
log "Mounting volume: #{volume_id} on #{device}"
|
|
649
|
+
self.mount(volume_id, device)
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
if @tags.has_key?(:security_group_ids)
|
|
653
|
+
self.set_security_groups(@tags[:security_group_ids].split(","))
|
|
654
|
+
end
|
|
655
|
+
if @tags.has_key?(:security_groups_foreign)
|
|
656
|
+
self.authorize_sg_ingress(@tags[:security_groups_foreign].split(","))
|
|
657
|
+
end
|
|
658
|
+
|
|
659
|
+
# if any of the above fails, we probably do not want it in the lb
|
|
660
|
+
if @tags.has_key?(:elastic_lb)
|
|
661
|
+
self.add_to_lb(@tags[:elastic_lb])
|
|
662
|
+
log "Adding instance: #{@id} to '#{@tags[:elastic_lb]}' load balancer"
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
def wait(options = {:timeout => 300, :desired_status => "running"})
|
|
666
|
+
t0 = Time.now.to_i
|
|
667
|
+
begin
|
|
668
|
+
sleep 10
|
|
669
|
+
log "Waiting on instance: #{@region.region}://#{@id} - current state: #{self.state}"
|
|
670
|
+
return if Time.now.to_i - t0 > options[:timeout]
|
|
671
|
+
end while self.state(use_cached_state = false) != options[:desired_status]
|
|
672
|
+
end
|
|
673
|
+
|
|
674
|
+
def set_security_groups(groups)
|
|
675
|
+
# only works on instances in a vpc
|
|
676
|
+
@region.ec2.modify_instance_attribute({:instance_id => @id, :groups => groups})
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
# Does a security group allow ingress on a port for the public IP of this instance
|
|
680
|
+
# @param group_port [String] - security_group:port like "sg_xxxxxx:8080"
|
|
681
|
+
# @return [Boolean] true if the security group allows ingress for the public IP of this instance on a certain port
|
|
682
|
+
def has_sg_rule?(group_port)
|
|
683
|
+
options = get_simple_sg_options(group_port)
|
|
684
|
+
options_cidr_ip = options[:ip_permissions][0][:ip_ranges][0][:cidr_ip]
|
|
685
|
+
group_id = options[:group_id]
|
|
686
|
+
raise "missing security group_id" if group_id.nil?
|
|
687
|
+
sg = @region.ec2.describe_security_groups(:group_ids => [group_id]).data.security_groups[0]
|
|
688
|
+
sg.ip_permissions.each do |p|
|
|
689
|
+
if p.ip_protocol == "tcp" &&
|
|
690
|
+
p.from_port == options[:ip_permissions][0][:from_port] &&
|
|
691
|
+
p.to_port == options[:ip_permissions][0][:to_port]
|
|
692
|
+
p[:ip_ranges].each do |ipr|
|
|
693
|
+
return true if ipr.cidr_ip == options_cidr_ip
|
|
694
|
+
end
|
|
695
|
+
end
|
|
696
|
+
end
|
|
697
|
+
false
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# authorize security group ingress for public ip of this instance on port
|
|
701
|
+
# @param groups [Array] - each element is String: "security_group_id:port". For example: ["sg-0xxxxx:80", "sg-0xxxxx:8443", "sg-0yyyyy:3000"]
|
|
702
|
+
def authorize_sg_ingress(groups)
|
|
703
|
+
raise "no public ip" unless @public_ip.to_s.match /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
|
|
704
|
+
groups.each do |gp|
|
|
705
|
+
options = get_simple_sg_options(gp)
|
|
706
|
+
if has_sg_rule?(gp)
|
|
707
|
+
log "security group rule #{gp} for #{self.public_ip} already exists"
|
|
708
|
+
else
|
|
709
|
+
@region.ec2.authorize_security_group_ingress options
|
|
710
|
+
log "added #{self.public_ip} to security group for :port #{gp}"
|
|
711
|
+
end
|
|
712
|
+
end
|
|
713
|
+
end
|
|
714
|
+
|
|
715
|
+
# revoke security group ingress for public ip of this instance on port
|
|
716
|
+
# @param groups [Array] - each element is String: "security_group_id:port". For example: ["sg-0xxxxx:80", "sg-0xxxxx:8443", "sg-0yyyyy:3000"]
|
|
717
|
+
def revoke_sg_ingress(groups)
|
|
718
|
+
# revoke the public ip of this instance for ingress on port for security group
|
|
719
|
+
# groups is array of strings: security_group_id:port
|
|
720
|
+
raise "no public ip" unless @public_ip.to_s.match /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
|
|
721
|
+
groups.each do |gp|
|
|
722
|
+
options = get_simple_sg_options(gp)
|
|
723
|
+
if has_sg_rule?(gp)
|
|
724
|
+
@region.ec2.revoke_security_group_ingress options
|
|
725
|
+
log "removed #{self.public_ip} from security group for :port #{gp}"
|
|
726
|
+
else
|
|
727
|
+
log "not removing #{self.public_ip} rule #{gp} because it does not exist"
|
|
728
|
+
end
|
|
729
|
+
end
|
|
730
|
+
end
|
|
731
|
+
def mount(volume_id, device)
|
|
732
|
+
@region.ec2.attach_volume({:instance_id => @id,
|
|
733
|
+
:volume_id => volume_id,
|
|
734
|
+
:device => device})
|
|
735
|
+
end
|
|
736
|
+
|
|
737
|
+
# construct hash for authorize/revoke ingress and has_sg_rule?
|
|
738
|
+
# @param group_id_port [String] - security_group:port like "sg_xxxxxx:8080"
|
|
739
|
+
# @return [Hash] - Hash for ec2 call for security group management
|
|
740
|
+
def get_simple_sg_options(group_id_port)
|
|
741
|
+
security_group_id, port = group_id_port.split(':')
|
|
742
|
+
port = port.to_s.to_i
|
|
743
|
+
raise "no security group id" unless security_group_id.to_s.length > 0
|
|
744
|
+
raise "no, or invalid port" unless port.to_s.to_i > 0
|
|
745
|
+
{:group_id => security_group_id,
|
|
746
|
+
:ip_permissions => [ :ip_protocol => "tcp",
|
|
747
|
+
:from_port => port,
|
|
748
|
+
:to_port => port,
|
|
749
|
+
:ip_ranges => [:cidr_ip => "#{self.public_ip}/32"]
|
|
750
|
+
]
|
|
751
|
+
}
|
|
752
|
+
end
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
end
|
|
756
|
+
end
|