olery-aws 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4592a112c2bfde7e1fc50a21e6f06e6d5308de8ccb1c6c6291494df50184e732
4
+ data.tar.gz: b6e2b32c49a1859ae05298850f081e55173b39ddb4605f122a63c03dc07ec4a2
5
+ SHA512:
6
+ metadata.gz: 44679fc3c631095891f66d11369119512136e06d8d6df86616d5b68e7256cc89106bb53c28781394c0c89c569ff91cc62084d1d6922ac0db55d14e30f4eca8b3
7
+ data.tar.gz: 7c6d2d6ddd2faf261fc64879759bd5cdf1cbbd1a95dbec7aba3834e4160dfa71b001ddeacc0dd977393bb294872a1a9b60bdaa5a05d32b95313b543bddc31cdd
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ # Olery AWS
2
+
3
+ Command-line tools for interacting with the Olery services running on AWS. This
4
+ includes tools for rotating instances, listing running instances, retrieving
5
+ information about autoscaling groups, etc.
6
+
7
+ ## Requirements
8
+
9
+ * Ruby 2.0 or newer
10
+ * AWS credentials (e.g. in `~/.bashrc`)
11
+
12
+ ## Installation
13
+
14
+ Install all the required Gems:
15
+
16
+ bundle install
17
+
18
+ Install the application as a Gem (so you can use it everywhere):
19
+
20
+ rake install
21
+
22
+ Now you can use it, for example:
23
+
24
+ olery-aws rotate kaf_processor-olery-com
data/bin/olery-aws ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/olery/aws'
4
+
5
+ command = ARGV[0]
6
+
7
+ if command and Olery::AWS::Command.exists?(command)
8
+ cmd_class = Olery::AWS::Command.get(command)
9
+ cmd_opts = cmd_class.parse(ARGV[1..-1])
10
+
11
+ if cmd_class.instance_method(:initialize).arity != 0
12
+ instance = cmd_class.new(cmd_opts.to_hash)
13
+ else
14
+ instance = cmd_class.new
15
+ end
16
+
17
+ if instance.method(:run).arity != 0
18
+ instance.run(*cmd_opts.arguments)
19
+ else
20
+ instance.run
21
+ end
22
+ else
23
+ opts = Slop.parse do |opt|
24
+ opt.banner = 'Usage: olery-aws [COMMAND] [OPTIONS]'
25
+
26
+ opt.separator <<-EOF
27
+
28
+ Examples:
29
+ olery-aws instances --running --grep reputation
30
+ olery-aws rotate scrapers-high-as-group-1
31
+
32
+ Commands:
33
+ #{Olery::AWS::Command.command_descriptions.join("\n ")}
34
+ EOF
35
+
36
+ opt.separator 'Options:'
37
+
38
+ opt.on '-h', '--help', 'Shows this help message' do
39
+ puts opt
40
+ exit
41
+ end
42
+
43
+ opt.on '--version', 'Shows version information' do
44
+ puts "olery-aws v#{Olery::AWS::VERSION} on #{RUBY_DESCRIPTION}"
45
+ exit
46
+ end
47
+ end
48
+
49
+ puts opts
50
+ end
51
+
52
+ # vim: set ft=ruby:
@@ -0,0 +1,69 @@
1
+ module Olery
2
+ module AWS
3
+ module Command
4
+ ##
5
+ # Command for listing autoscaling groups and their details.
6
+ #
7
+ class AutoscalingGroups
8
+ NAME = 'as-groups'
9
+ DESCRIPTION = 'Lists autoscaling groups and their details'
10
+
11
+ ##
12
+ # @param [Array] argv
13
+ # @return [Slop]
14
+ #
15
+ def self.parse(argv)
16
+ Slop.parse(argv) do |opt|
17
+ opt.banner = "Usage: olery-aws #{NAME} [OPTIONS]"
18
+
19
+ opt.separator <<-EOF
20
+
21
+ Examples:
22
+ olery-aws #{NAME}
23
+ olery-aws #{NAME} --pattern scraper
24
+ EOF
25
+
26
+ opt.separator 'Options:'
27
+
28
+ opt.regexp '-p', '--pattern', 'Matches group names using a Regexp'
29
+
30
+ opt.on '-h', '--help', 'Shows this help message' do
31
+ puts opt
32
+ exit
33
+ end
34
+ end
35
+ end
36
+
37
+ ##
38
+ # @param [Regexp] pattern When given only autoscaling groups who's names
39
+ # match this pattern will be included.
40
+ #
41
+ def initialize(pattern: nil)
42
+ @pattern = pattern
43
+ end
44
+
45
+ def run
46
+ auto_scaling_groups.each do |group|
47
+ next if @pattern && group.auto_scaling_group_name !~ @pattern
48
+
49
+ puts group.auto_scaling_group_name
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ # @return [Array]
56
+ def auto_scaling_groups
57
+ response = auto_scaling.describe_auto_scaling_groups
58
+
59
+ response.auto_scaling_groups
60
+ end
61
+
62
+ # @return [Aws::AutoScaling::Client]
63
+ def auto_scaling
64
+ @auto_scaling ||= Aws::AutoScaling::Client.new
65
+ end
66
+ end # AutoscalingGroups
67
+ end # Command
68
+ end # AWS
69
+ end # Olery
@@ -0,0 +1,108 @@
1
+ module Olery
2
+ module AWS
3
+ module Command
4
+ ##
5
+ # Command for listing EC2 instances and basic details.
6
+ #
7
+ class Instances
8
+ NAME = 'instances'
9
+ DESCRIPTION = 'Lists EC2 instances'
10
+
11
+ ##
12
+ # @param [Array] argv
13
+ # @return [Slop]
14
+ #
15
+ def self.parse(argv)
16
+ Slop.parse(argv) do |opt|
17
+ opt.banner = "Usage: olery-aws #{NAME} [OPTIONS]"
18
+
19
+ opt.separator <<-EOF
20
+
21
+ Examples:
22
+ olery-aws #{NAME}
23
+ olery-aws #{NAME} --running
24
+ olery-aws #{NAME} --running --name api
25
+ EOF
26
+
27
+ opt.separator 'Options:'
28
+
29
+ opt.bool '-r', '--running', 'List running instances only'
30
+
31
+ opt.regexp '-p', '--pattern', 'List instances with a matching name'
32
+
33
+ opt.on '-h', '--help', 'Shows this help message' do
34
+ puts opt
35
+ exit
36
+ end
37
+ end
38
+ end
39
+
40
+ ##
41
+ # @param [Regexp] pattern
42
+ # @param [TrueClass|FalseClass] running
43
+ #
44
+ def initialize(pattern: nil, running: false)
45
+ @pattern = pattern
46
+ @running = running
47
+ end
48
+
49
+ def run
50
+ if filters.empty?
51
+ instances = ec2.instances
52
+ else
53
+ instances = ec2.instances(:filters => filters)
54
+ end
55
+
56
+ table = Table.new
57
+
58
+ instances.each do |instance|
59
+ name = instance_name(instance)
60
+
61
+ if (@pattern and name and name =~ @pattern) or !@pattern
62
+ table << [
63
+ instance.instance_id,
64
+ instance.state.name,
65
+ instance.public_dns_name,
66
+ name.to_s
67
+ ]
68
+ end
69
+ end
70
+
71
+ table.sort_column!(3)
72
+
73
+ puts table.to_s
74
+ end
75
+
76
+ # @return [Array]
77
+ def filters
78
+ filters = []
79
+
80
+ if @running
81
+ filters << {:name => 'instance-state-name', :values => %w{running}}
82
+ end
83
+
84
+ filters
85
+ end
86
+
87
+ ##
88
+ # Returns the name of an EC2 instance.
89
+ #
90
+ # @param [Aws::EC2::Instance] instance
91
+ # @return [String|NilClass]
92
+ #
93
+ def instance_name(instance)
94
+ tag = instance.tags.find { |tag| tag.key == 'Name' }
95
+
96
+ tag ? tag.value : nil
97
+ end
98
+
99
+ private
100
+
101
+ # @return [Aws::EC2::Client]
102
+ def ec2
103
+ @ec2 ||= Aws::EC2::Resource.new
104
+ end
105
+ end # Instances
106
+ end # Command
107
+ end # AWS
108
+ end # Olery
@@ -0,0 +1,563 @@
1
+ module Olery
2
+ module AWS
3
+ module Command
4
+ ##
5
+ # Command for rotating EC2 instances in an autoscaling group.
6
+ #
7
+ # Two different rotation strategies can be used:
8
+ #
9
+ # 1. Rotating via an ELB (if present)
10
+ # 2. Simply scaling down and back up again
11
+ #
12
+ # If an ELB is present the autoscaling group capacity is doubled and the
13
+ # old instances terminated. This ensures that the autoscaling group
14
+ # remains operational during a deploy.
15
+ #
16
+ # The 2nd strategy is used when no ELB is present, this is usually only
17
+ # the case for autoscaling groups used for background processing. In these
18
+ # cases downtime doesn't really matter since users won't notice it.
19
+ #
20
+ # ## Synchronization
21
+ #
22
+ # To ensure deployments operate smoothly only 1 entity can deploy to the
23
+ # same autoscaling group at a given time. Synchronization is done by
24
+ # emulating a semaphore using autoscaling group tags. This setup should
25
+ # prevent parallel deploys in 99,99% of the cases (fingers crossed).
26
+ #
27
+ class Rotate
28
+ NAME = 'rotate'
29
+ DESCRIPTION = 'Rotates EC2 instances in an autoscaling group'
30
+
31
+ # The name of the tag used for locking deployments
32
+ LOCK_TAG = 'olery-deployment-lock'
33
+
34
+ ##
35
+ # @param [Array] argv
36
+ # @return [Slop]
37
+ #
38
+ def self.parse(argv)
39
+ Slop.parse(argv) do |opt|
40
+ opt.banner = "Usage: olery-aws #{NAME} [OPTIONS]"
41
+
42
+ opt.separator <<-EOF
43
+
44
+ Examples:
45
+ olery-aws #{NAME} scrapers-high-as-group-1
46
+ EOF
47
+
48
+ opt.separator 'Options:'
49
+
50
+ opt.on '-h', '--help', 'Shows this help message' do
51
+ puts opt
52
+ exit
53
+ end
54
+ end
55
+ end
56
+
57
+ def initialize
58
+ @logger = Logger.new(STDERR)
59
+
60
+ @logger.formatter = proc do |level, time, prog, msg|
61
+ "#{time.strftime('%H:%M:%S')} #{level}: #{msg}\n"
62
+ end
63
+ end
64
+
65
+ ##
66
+ # @param [String] group_name The name of the autoscaling group.
67
+ #
68
+ def run(group_name)
69
+ info 'Acquiring deployment lock'
70
+
71
+ synchronize_group(group_name) do
72
+ info 'Lock acquired'
73
+
74
+ group = get_group(group_name)
75
+
76
+ if group.instances.length > 0
77
+ if has_elbs?(group)
78
+ rotate_elb(group)
79
+ else
80
+ rotate(group)
81
+ end
82
+ else
83
+ info 'No running instances in autoscaling group'
84
+ end
85
+ end
86
+ end
87
+
88
+ ##
89
+ # Rotates all instances attached to an autoscaling group with a number
90
+ # of ELBs.
91
+ #
92
+ # The process for this is as following:
93
+ #
94
+ # 1. Double the capacity of the autoscaling group.
95
+ # 2. Wait for all instances to be registered with all ELBs.
96
+ # 3. Wait for all instances to be in service.
97
+ # 4. Detach the old instances and wait for them to be de-registered from
98
+ # the autoscaling group.
99
+ # 5. Terminate the old instances and wait for them to be terminated.
100
+ # 6. Reset the autoscaling group's settings to their original values.
101
+ #
102
+ # @param [Struct] group
103
+ #
104
+ def rotate_elb(group)
105
+ info 'Disabling terminating of instances'
106
+
107
+ suspend_processes(group.auto_scaling_group_name, 'Terminate')
108
+
109
+ new_size = group.max_size * 2
110
+ new_desired = group.desired_capacity * 2
111
+
112
+ info "Doubling autoscaling capacity to #{new_size}"
113
+
114
+ update_group(
115
+ group.auto_scaling_group_name,
116
+ max_size: new_size,
117
+ desired_capacity: new_desired,
118
+ )
119
+
120
+ info 'Waiting for ELBs to register instances'
121
+
122
+ wait_for_elbs group.load_balancer_names, new_desired
123
+
124
+ info 'Waiting for all instances to be in service'
125
+
126
+ group.load_balancer_names.each do |elb_name|
127
+ wait_until_in_service(elb_name)
128
+ end
129
+
130
+ instance_ids = group.instances.map(&:instance_id)
131
+
132
+ info 'Disabling launching of new instances'
133
+
134
+ suspend_processes(group.auto_scaling_group_name, 'Launch')
135
+
136
+ info 'Detaching old instances'
137
+
138
+ detach_instances(group.auto_scaling_group_name, instance_ids)
139
+
140
+ info 'Waiting for instances to be removed from autoscaling group'
141
+
142
+ wait_until_removed_from_group(
143
+ group.auto_scaling_group_name,
144
+ instance_ids
145
+ )
146
+
147
+ info 'Terminating old instances'
148
+
149
+ terminate_instances(instance_ids)
150
+
151
+ wait_until_terminated(instance_ids)
152
+
153
+ # In the event of any errors/timeouts/etc we want to be absolutely sure
154
+ # the autoscaling group is back in its initial state.
155
+ ensure
156
+ info 'Restoring autoscaling group'
157
+
158
+ update_group(
159
+ group.auto_scaling_group_name,
160
+ :max_size => group.max_size
161
+ )
162
+
163
+ info 'Re-enabling all autoscaling processes'
164
+
165
+ resume_processes(group.auto_scaling_group_name)
166
+ end
167
+
168
+ ##
169
+ # Terminates all instances in an autoscaling group and adds the same
170
+ # amount of new instances.
171
+ #
172
+ # The process for this is as following:
173
+ #
174
+ # 1. Scale down to 0 instances and wait for all instances to be
175
+ # terminated.
176
+ # 2. Scale back up to the original capacity and wait for all instances
177
+ # to be in service.
178
+ # 3. Reset the autoscaling group's settings to their original values.
179
+ #
180
+ # @param [Struct] group
181
+ #
182
+ def rotate(group)
183
+ info 'Disabling launching of new instances'
184
+
185
+ suspend_processes(group.auto_scaling_group_name, 'Launch')
186
+
187
+ info 'Setting minimum amount of instances to 0'
188
+
189
+ instance_ids = group.instances.map(&:instance_id)
190
+
191
+ info 'Scaling down to 0 instances'
192
+
193
+ update_group(
194
+ group.auto_scaling_group_name,
195
+ :min_size => 0,
196
+ :desired_capacity => 0
197
+ )
198
+
199
+ wait_until_terminated(instance_ids)
200
+
201
+ info 'Waiting for instances to be removed from autoscaling group'
202
+
203
+ wait_until_removed_from_group(
204
+ group.auto_scaling_group_name,
205
+ instance_ids
206
+ )
207
+
208
+ resume_processes(group.auto_scaling_group_name)
209
+
210
+ info 'Disabling terminating of instances'
211
+
212
+ suspend_processes(group.auto_scaling_group_name, 'Terminate')
213
+
214
+ info 'Scaling back to original capacity'
215
+
216
+ update_group(
217
+ group.auto_scaling_group_name,
218
+ :desired_capacity => group.desired_capacity
219
+ )
220
+
221
+ # Spot instances may take a long time to start up due to pricing. As
222
+ # such, in case an autoscaling group runs spot instances we _won't_
223
+ # wait for the instances to start up.
224
+ unless spot_instances?(group.auto_scaling_group_name)
225
+ wait_until_group_capacity(
226
+ group.auto_scaling_group_name,
227
+ group.desired_capacity
228
+ )
229
+
230
+ info 'Waiting for instances to start'
231
+
232
+ new_group = get_group(group.auto_scaling_group_name)
233
+ new_instance_ids = new_group.instances.map(&:instance_id)
234
+
235
+ wait_until_running(new_instance_ids) unless new_instance_ids.empty?
236
+ end
237
+
238
+ # In the event of any errors/timeouts/etc we want to be absolutely sure
239
+ # the autoscaling group is back in its initial state.
240
+ ensure
241
+ info 'Restoring autoscaling group'
242
+
243
+ update_group(
244
+ group.auto_scaling_group_name,
245
+ :min_size => group.min_size
246
+ )
247
+
248
+ info 'Re-enabling all autoscaling processes'
249
+
250
+ resume_processes(group.auto_scaling_group_name)
251
+ end
252
+
253
+ ##
254
+ # @param [String] group_name
255
+ # @param [Array] processes
256
+ #
257
+ def suspend_processes(group_name, *processes)
258
+ auto_scaling.suspend_processes(
259
+ :auto_scaling_group_name => group_name,
260
+ :scaling_processes => processes
261
+ )
262
+ end
263
+
264
+ ##
265
+ # @param [String] group_name
266
+ #
267
+ def resume_processes(group_name)
268
+ auto_scaling.resume_processes(:auto_scaling_group_name => group_name)
269
+ end
270
+
271
+ ##
272
+ # @param [String] group_name
273
+ # @return [Mixed]
274
+ #
275
+ def get_group(group_name)
276
+ group = auto_scaling.describe_auto_scaling_groups(
277
+ :auto_scaling_group_names => [group_name]
278
+ )
279
+
280
+ group.auto_scaling_groups[0]
281
+ end
282
+
283
+ ##
284
+ # @param [Struct] group
285
+ # @return [TrueClass|FalseClass]
286
+ #
287
+ def has_elbs?(group)
288
+ group.load_balancer_names.length > 0
289
+ end
290
+
291
+ ##
292
+ # @param [String] name
293
+ # @param [Hash] options
294
+ #
295
+ def update_group(name, options)
296
+ auto_scaling.update_auto_scaling_group(
297
+ options.merge(:auto_scaling_group_name => name)
298
+ )
299
+ end
300
+
301
+ ##
302
+ # @param [Array] elb_names
303
+ # @param [Struct] group
304
+ #
305
+ def wait_for_elbs(elb_names, instances)
306
+ loop_with_limit do
307
+ response = load_balancing.describe_load_balancers(
308
+ :load_balancer_names => elb_names
309
+ )
310
+
311
+ ok_elbs = response.load_balancer_descriptions.count do |elb|
312
+ elb.instances.length == instances
313
+ end
314
+
315
+ break if ok_elbs == elb_names.length
316
+
317
+ sleep(5)
318
+ end
319
+ end
320
+
321
+ ##
322
+ # @param [String] elb_name
323
+ #
324
+ def wait_until_in_service(elb_name)
325
+ load_balancing.wait_until(
326
+ :instance_in_service,
327
+ :load_balancer_name => elb_name
328
+ ) do |waiter|
329
+ waiter.delay = 30
330
+ end
331
+ end
332
+
333
+ ##
334
+ # @param [String] group_name
335
+ # @param [Fixnum] capacity
336
+ #
337
+ def wait_until_group_capacity(group_name, capacity)
338
+ loop_with_limit do
339
+ group = get_group(group_name)
340
+ current = group.instances.length
341
+
342
+ break if current == capacity
343
+
344
+ sleep(5)
345
+ end
346
+ end
347
+
348
+ ##
349
+ # @param [Array] ids
350
+ #
351
+ def terminate_instances(ids)
352
+ ec2.terminate_instances(:instance_ids => ids)
353
+ end
354
+
355
+ ##
356
+ # @param [String] group_name
357
+ # @param [Array] ids
358
+ #
359
+ def detach_instances(group_name, ids)
360
+ auto_scaling.detach_instances(
361
+ :auto_scaling_group_name => group_name,
362
+ :instance_ids => ids,
363
+ :should_decrement_desired_capacity => true
364
+ )
365
+ end
366
+
367
+ ##
368
+ # Waits until the given instances are terminated.
369
+ #
370
+ # @param [Array] ids
371
+ #
372
+ def wait_until_terminated(ids)
373
+ ec2.wait_until(:instance_terminated, :instance_ids => ids) do |waiter|
374
+ waiter.delay = 30
375
+ waiter.max_attempts = 60
376
+ end
377
+ end
378
+
379
+ ##
380
+ # Waits until the given instances are running.
381
+ #
382
+ # @param [Array] ids
383
+ #
384
+ def wait_until_running(ids)
385
+ ec2.wait_until(:instance_running, :instance_ids => ids) do |waiter|
386
+ waiter.delay = 30
387
+ waiter.max_attempts = 60
388
+ end
389
+ end
390
+
391
+ ##
392
+ # Waits until the given instances are removed from the autoscaling
393
+ # group.
394
+ #
395
+ # @param [String] group_name
396
+ # @param [Array] instance_ids
397
+ #
398
+ def wait_until_removed_from_group(group_name, instance_ids)
399
+ loop_with_limit do
400
+ group = get_group(group_name)
401
+ current = group.instances.map(&:instance_id)
402
+
403
+ break if (current & instance_ids).empty?
404
+
405
+ sleep(5)
406
+ end
407
+ end
408
+
409
+ ##
410
+ # Yields the given block at most `max` times.
411
+ #
412
+ def loop_with_limit(max = 60)
413
+ max.times { yield }
414
+ end
415
+
416
+ ##
417
+ # Locks an autoscaling group to prevent concurrent deploys from messing
418
+ # up the capacity. Once locked the block is yielded, once the block
419
+ # returns the lock is released.
420
+ #
421
+ # @param [String] group_name
422
+ #
423
+ def synchronize_group(group_name)
424
+ loop do
425
+ # Somebody else has the lock
426
+ sleep(5) while group_locked_by(group_name)
427
+
428
+ # Make sure we only acquire the lock if nobody else did so in the
429
+ # mean time.
430
+ unless group_locked_by(group_name)
431
+ add_group_tag(group_name, LOCK_TAG, uuid)
432
+ end
433
+
434
+ held_by = group_locked_by(group_name)
435
+
436
+ # Did we _actually_ acquire the lock, or did somebody else overwrite
437
+ # ours?
438
+ if held_by == uuid
439
+ yield
440
+ break
441
+ end
442
+ end
443
+
444
+ # Lets try to not leave behind a locked group that needs manual
445
+ # unlocking.
446
+ ensure
447
+ unlock_group(group_name)
448
+ end
449
+
450
+ ##
451
+ # Unlocks an autoscaling group.
452
+ #
453
+ # @param [String] group_name
454
+ #
455
+ def unlock_group(group_name)
456
+ if group_locked_by(group_name) == uuid
457
+ remove_group_tag(group_name, LOCK_TAG)
458
+ end
459
+ end
460
+
461
+ ##
462
+ # @param [String] group_name
463
+ # @return [String|NilClass]
464
+ #
465
+ def group_locked_by(group_name)
466
+ tag = get_group(group_name).tags.find { |tag| tag.key == LOCK_TAG }
467
+
468
+ tag ? tag.value : nil
469
+ end
470
+
471
+ ##
472
+ # @param [String] group_name
473
+ # @param [String] tag_name
474
+ # @param [String] tag_value
475
+ #
476
+ def add_group_tag(group_name, tag_name, tag_value)
477
+ auto_scaling.create_or_update_tags(
478
+ :tags => [
479
+ {
480
+ :resource_id => group_name,
481
+ :resource_type => 'auto-scaling-group',
482
+ :key => tag_name,
483
+ :value => tag_value,
484
+ :propagate_at_launch => false
485
+ }
486
+ ]
487
+ )
488
+ end
489
+
490
+ ##
491
+ # @param [String] group_name
492
+ # @param [String] tag_name
493
+ #
494
+ def remove_group_tag(group_name, tag_name)
495
+ auto_scaling.delete_tags(
496
+ :tags => [
497
+ {
498
+ :resource_id => group_name,
499
+ :resource_type => 'auto-scaling-group',
500
+ :key => tag_name
501
+ }
502
+ ]
503
+ )
504
+ end
505
+
506
+ ##
507
+ # Checks if an autoscaling group has any spot instances.
508
+ #
509
+ # @param [String] group_name
510
+ # @return [TrueClass|FalseClass]
511
+ #
512
+ def spot_instances?(group_name)
513
+ group = get_group(group_name)
514
+ group_launch_spot? group
515
+ end
516
+
517
+ ##
518
+ # @param [String] group
519
+ # @return [Boolean]
520
+ #
521
+ def group_launch_spot? group
522
+ if config_name = group.launch_configuration_name
523
+ response = auto_scaling.describe_launch_configurations launch_configuration_names: [config_name]
524
+ !!response.launch_configurations[0].spot_price
525
+ else
526
+ versions = ec2.describe_launch_template_versions launch_template_id: group.launch_template.launch_template_id
527
+ final = versions.launch_template_versions.first
528
+ final.launch_template_data.instance_market_options.market_type == 'spot'
529
+ end
530
+ end
531
+
532
+ private
533
+
534
+ ##
535
+ # @param [String] message
536
+ #
537
+ def info(message)
538
+ @logger.info(message)
539
+ end
540
+
541
+ # @return [Aws::AutoScaling::Client]
542
+ def auto_scaling
543
+ @auto_scaling ||= Aws::AutoScaling::Client.new
544
+ end
545
+
546
+ # @return [Aws::ElasticLoadBalancing::Client]
547
+ def load_balancing
548
+ @load_balancing ||= Aws::ElasticLoadBalancing::Client.new
549
+ end
550
+
551
+ # @return [Aws::EC2::Client]
552
+ def ec2
553
+ @ec2 ||= Aws::EC2::Client.new
554
+ end
555
+
556
+ # @return [String]
557
+ def uuid
558
+ @uuid ||= UUID.generate
559
+ end
560
+ end # Rotate
561
+ end # Command
562
+ end # AWS
563
+ end # Olery
@@ -0,0 +1,49 @@
1
+ module Olery
2
+ module AWS
3
+ module Command
4
+ ##
5
+ # Returns all the available command classes.
6
+ #
7
+ # @return [Array<Class>]
8
+ #
9
+ def self.commands
10
+ Command.constants.map { |name| Command.const_get(name) }
11
+ end
12
+
13
+ ##
14
+ # Returns an Array with command names and their descriptions. The command
15
+ # names contain padding so they can be aligned.
16
+ #
17
+ # @return [Array]
18
+ #
19
+ def self.command_descriptions
20
+ lines = commands.map { |const| [const::NAME, const::DESCRIPTION] }
21
+ names = Formatting.pad_strings(lines.map { |line| line[0] })
22
+
23
+ names.sort.map.with_index do |name, index|
24
+ "#{name} #{lines[index][1]}"
25
+ end
26
+ end
27
+
28
+ ##
29
+ # Returns `true` if the given command exists.
30
+ #
31
+ # @param [String] name
32
+ # @return [TrueClass|FalseClass]
33
+ #
34
+ def self.exists?(name)
35
+ commands.map { |const| const::NAME }.include?(name)
36
+ end
37
+
38
+ ##
39
+ # Returns the command for the given name.
40
+ #
41
+ # @param [String] name
42
+ # @return [Class]
43
+ #
44
+ def self.get(name)
45
+ commands.find { |const| const::NAME == name }
46
+ end
47
+ end # Command
48
+ end # AWS
49
+ end # Olery
@@ -0,0 +1,26 @@
1
+ module Olery
2
+ module AWS
3
+ ##
4
+ # Helper module for formatting output, aligning text, etc.
5
+ #
6
+ module Formatting
7
+ module_function
8
+
9
+ ##
10
+ # Returns an Array with all strings padded with whitespace.
11
+ #
12
+ # @example
13
+ # pad_strings(%w{foo foobar}) # => ["foo ", "foobar"]
14
+ #
15
+ # @param [Array] values
16
+ # @return [Array]
17
+ #
18
+ def pad_strings(values)
19
+ longest = values.sort { |left, right| right.length <=> left.length }
20
+ padding = longest[0] ? longest[0].length : 0
21
+
22
+ values.map { |value| value.ljust(padding, ' ') }
23
+ end
24
+ end # Formatting
25
+ end # AWS
26
+ end # Olery
@@ -0,0 +1,88 @@
1
+ module Olery
2
+ module AWS
3
+ ##
4
+ # Class for generating an ASCII table with the columns aligned. The output
5
+ # format is grep/awk/etc friendly.
6
+ #
7
+ class Table
8
+ # @return [Array]
9
+ attr_reader :rows
10
+
11
+ def initialize
12
+ @rows = []
13
+
14
+ @column_count = 0
15
+ end
16
+
17
+ ##
18
+ # Adds a new row to the table.
19
+ #
20
+ # @param [Array] row
21
+ #
22
+ def push(row)
23
+ @rows << row
24
+
25
+ if row.length > @column_count
26
+ @column_count = row.length
27
+ end
28
+ end
29
+
30
+ alias_method :<<, :push
31
+
32
+ ##
33
+ # Sorts the table rows.
34
+ #
35
+ # @yieldparam [Array]
36
+ #
37
+ def sort!(&block)
38
+ @rows.sort_by!(&block)
39
+ end
40
+
41
+ ##
42
+ # Sorts all rows by the given column index.
43
+ #
44
+ # @param [Fixnum] column
45
+ #
46
+ def sort_column!(column)
47
+ sort! { |row| row[column] }
48
+ end
49
+
50
+ # @return [String]
51
+ def to_s
52
+ lengths = column_lengths
53
+ lines = []
54
+
55
+ @rows.each do |row|
56
+ line = row.map.with_index do |column, index|
57
+ column.ljust(lengths[index], ' ')
58
+ end
59
+
60
+ lines << line.join(' ')
61
+ end
62
+
63
+ lines.join("\n")
64
+ end
65
+
66
+ private
67
+
68
+ ##
69
+ # Returns an array containing the longest length for every column.
70
+ #
71
+ # @return [Array]
72
+ #
73
+ def column_lengths
74
+ lengths = Array.new(@column_count, 0)
75
+
76
+ @rows.each do |row|
77
+ row.each_with_index do |column, index|
78
+ length = column.length
79
+
80
+ lengths[index] = length if length > lengths[index]
81
+ end
82
+ end
83
+
84
+ lengths
85
+ end
86
+ end # Table
87
+ end # AWS
88
+ end # Olery
@@ -0,0 +1,7 @@
1
+ module Olery
2
+ module AWS
3
+
4
+ VERSION = '1.2.1'
5
+
6
+ end
7
+ end
data/lib/olery/aws.rb ADDED
@@ -0,0 +1,13 @@
1
+ require 'aws-sdk'
2
+ require 'slop'
3
+ require 'uuid'
4
+
5
+ require 'logger'
6
+
7
+ require_relative 'aws/version'
8
+ require_relative 'aws/command'
9
+ require_relative 'aws/formatting'
10
+ require_relative 'aws/table'
11
+ require_relative 'aws/command/autoscaling_groups'
12
+ require_relative 'aws/command/instances'
13
+ require_relative 'aws/command/rotate'
data/olery-aws.gemspec ADDED
@@ -0,0 +1,30 @@
1
+ require File.expand_path('../lib/olery/aws/version', __FILE__)
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'olery-aws'
5
+ gem.version = Olery::AWS::VERSION
6
+ gem.authors = ['Olery B.V.']
7
+ gem.email = 'development@olery.com'
8
+ gem.summary = 'Command-line tools for interacting with the Olery services running on AWS.'
9
+ gem.homepage = 'https://github.com/olery/olery-aws/'
10
+ gem.description = gem.summary
11
+ gem.executables = %w{olery-aws}
12
+
13
+ gem.files = Dir.glob([
14
+ 'bin/**/*',
15
+ 'lib/**/*.rb',
16
+ 'README.md',
17
+ '*.gemspec'
18
+ ]).select { |file| File.file?(file) }
19
+
20
+ gem.required_ruby_version = '>= 2.0'
21
+
22
+ gem.add_dependency 'aws-sdk', '~> 2.0'
23
+ gem.add_dependency 'slop', '~> 4.0'
24
+ gem.add_dependency 'uuid'
25
+
26
+ gem.add_development_dependency 'pry'
27
+ gem.add_development_dependency 'rake'
28
+ gem.add_development_dependency 'bundler'
29
+ gem.add_development_dependency 'rspec', '~> 3.0'
30
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: olery-aws
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Olery B.V.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-12-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: aws-sdk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: slop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: uuid
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: pry
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ description: Command-line tools for interacting with the Olery services running on
112
+ AWS.
113
+ email: development@olery.com
114
+ executables:
115
+ - olery-aws
116
+ extensions: []
117
+ extra_rdoc_files: []
118
+ files:
119
+ - README.md
120
+ - bin/olery-aws
121
+ - lib/olery/aws.rb
122
+ - lib/olery/aws/command.rb
123
+ - lib/olery/aws/command/autoscaling_groups.rb
124
+ - lib/olery/aws/command/instances.rb
125
+ - lib/olery/aws/command/rotate.rb
126
+ - lib/olery/aws/formatting.rb
127
+ - lib/olery/aws/table.rb
128
+ - lib/olery/aws/version.rb
129
+ - olery-aws.gemspec
130
+ homepage: https://github.com/olery/olery-aws/
131
+ licenses: []
132
+ metadata: {}
133
+ post_install_message:
134
+ rdoc_options: []
135
+ require_paths:
136
+ - lib
137
+ required_ruby_version: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - ">="
140
+ - !ruby/object:Gem::Version
141
+ version: '2.0'
142
+ required_rubygems_version: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ requirements: []
148
+ rubygems_version: 3.0.9
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Command-line tools for interacting with the Olery services running on AWS.
152
+ test_files: []