maws 0.8.0

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.
Files changed (50) hide show
  1. data/bin/maws +10 -0
  2. data/lib/maws/chunk_source.rb +41 -0
  3. data/lib/maws/command.rb +62 -0
  4. data/lib/maws/command_loader.rb +28 -0
  5. data/lib/maws/command_options_parser.rb +37 -0
  6. data/lib/maws/commands/configure.rb +287 -0
  7. data/lib/maws/commands/console.rb +38 -0
  8. data/lib/maws/commands/create.rb +25 -0
  9. data/lib/maws/commands/describe.rb +15 -0
  10. data/lib/maws/commands/destroy.rb +11 -0
  11. data/lib/maws/commands/elb-add.rb +17 -0
  12. data/lib/maws/commands/elb-describe.rb +23 -0
  13. data/lib/maws/commands/elb-disable-zones.rb +17 -0
  14. data/lib/maws/commands/elb-enable-zones.rb +18 -0
  15. data/lib/maws/commands/elb-remove.rb +16 -0
  16. data/lib/maws/commands/set-prefix.rb +24 -0
  17. data/lib/maws/commands/set-security-groups.rb +442 -0
  18. data/lib/maws/commands/start.rb +11 -0
  19. data/lib/maws/commands/status.rb +25 -0
  20. data/lib/maws/commands/stop.rb +11 -0
  21. data/lib/maws/commands/teardown.rb +11 -0
  22. data/lib/maws/commands/volumes-cleanup.rb +22 -0
  23. data/lib/maws/commands/volumes-status.rb +43 -0
  24. data/lib/maws/commands/wait.rb +61 -0
  25. data/lib/maws/connection.rb +121 -0
  26. data/lib/maws/core_ext/object.rb +5 -0
  27. data/lib/maws/description/ebs.rb +40 -0
  28. data/lib/maws/description/ec2.rb +72 -0
  29. data/lib/maws/description/elb.rb +52 -0
  30. data/lib/maws/description/rds.rb +47 -0
  31. data/lib/maws/description.rb +78 -0
  32. data/lib/maws/instance/ebs.rb +45 -0
  33. data/lib/maws/instance/ec2.rb +144 -0
  34. data/lib/maws/instance/elb.rb +92 -0
  35. data/lib/maws/instance/rds.rb +84 -0
  36. data/lib/maws/instance.rb +167 -0
  37. data/lib/maws/instance_collection.rb +98 -0
  38. data/lib/maws/instance_display.rb +84 -0
  39. data/lib/maws/instance_matcher.rb +27 -0
  40. data/lib/maws/loader.rb +173 -0
  41. data/lib/maws/logger.rb +66 -0
  42. data/lib/maws/mash.rb +9 -0
  43. data/lib/maws/maws.rb +102 -0
  44. data/lib/maws/profile_loader.rb +92 -0
  45. data/lib/maws/specification.rb +127 -0
  46. data/lib/maws/ssh.rb +7 -0
  47. data/lib/maws/trollop.rb +782 -0
  48. data/lib/maws/volumes_command.rb +29 -0
  49. data/lib/maws.rb +25 -0
  50. metadata +115 -0
@@ -0,0 +1,442 @@
1
+ require 'maws/command'
2
+
3
+ class SetSecurityGroups < Command
4
+ def description
5
+ "set-security-groups - create or update security groups from security rules configuration"
6
+ end
7
+
8
+ # override from command to ignore selection
9
+ def verify_options
10
+ if @config.command_line.region.blank?
11
+ Trollop::die "Region must be specified in command line options OR as 'default_region' in #{@config.config.paths.config}"
12
+ end
13
+ end
14
+
15
+ def run!
16
+ rules = @config.security_rules
17
+ if rules.empty?
18
+ info "No security rules to create security groups from"
19
+ return
20
+ end
21
+
22
+ @security_group_definitions = {}
23
+ rules.keys.map {|rules_set_name|
24
+ security_group_name = "#{@config.profile.name}-#{rules_set_name}"
25
+ service = if rules_set_name == 'ec2_default'
26
+ security_group_name = 'ec2_default'
27
+ :ec2
28
+ elsif rules_set_name == 'rds_default'
29
+ security_group_name = 'rds_default'
30
+ :rds
31
+ else
32
+ @config.combined[rules_set_name].service
33
+ end
34
+
35
+ @security_group_definitions[security_group_name] = {
36
+ :role => rules_set_name,
37
+ :rules => rules[rules_set_name],
38
+ :service => service.to_sym}
39
+ }
40
+
41
+ update_definitions_with_descriptions(@security_group_definitions)
42
+
43
+
44
+ @security_group_definitions.each { |name, definition|
45
+ create_security_group(name, definition) unless definition[:description]
46
+ }
47
+
48
+ update_definitions_with_descriptions(@security_group_definitions)
49
+ begin
50
+ update_definitions_rules_with_real_ids(@security_group_definitions)
51
+ rescue BadSecurityRule => err
52
+ info "Bad security rule: #{err.message}"
53
+ info "No AWS security rules will be updated."
54
+ return
55
+ end
56
+
57
+ @security_group_definitions.each { |name, definition|
58
+ info "#{definition[:service]} security group #{name}"
59
+
60
+ clear_out_security_group(name, definition)
61
+
62
+ # rds takes a while to propagate. this code handles errors with retries, but this sleep keeps the noise down
63
+ if definition[:service] == :rds
64
+ info "...waiting for revoke to take effect..."
65
+ sleep 20
66
+ end
67
+
68
+ set_security_group(name, definition)
69
+
70
+ info "done\n\n"
71
+ }
72
+ end
73
+
74
+ # sets :description, :group_id, :group_name and :owner_id for each definition
75
+ def update_definitions_with_descriptions(security_group_definitions)
76
+ ec2_sg_descriptions = connection.ec2.describe_security_groups
77
+ rds_sg_descriptions = connection.rds.describe_db_security_groups
78
+
79
+ # attach existing descriptions (if they exist) to sec group definition
80
+ security_group_definitions.each {|name, definition|
81
+ if definition[:service] == :ec2
82
+ definition[:description] = ec2_sg_descriptions.find {|description| description[:aws_group_name] == name }
83
+ if definition[:description]
84
+ definition[:group_id] = definition[:description][:group_id]
85
+ definition[:group_name] = definition[:description][:aws_group_name]
86
+ definition[:owner_id] = definition[:description][:aws_owner]
87
+ end
88
+ elsif definition[:service] == :rds
89
+ definition[:description] = rds_sg_descriptions.find {|description| description[:name] == name }
90
+ if definition[:description]
91
+ definition[:group_id] = definition[:description][:name]
92
+ definition[:group_name] = definition[:description][:name]
93
+ definition[:owner_id] = definition[:description][:owner_id]
94
+ end
95
+ end
96
+ }
97
+ end
98
+
99
+
100
+
101
+ def create_security_group(name, definition)
102
+ info "CREATING #{name}"
103
+
104
+ if definition[:service] == :ec2
105
+ connection.ec2.create_security_group(name, name) # description same as name
106
+ elsif definition[:service] == :rds
107
+ connection.rds.create_db_security_group(name, name)
108
+ end
109
+ end
110
+
111
+ def clear_out_security_group(name, definition)
112
+ info " clearing out #{name}"
113
+ description = definition[:description]
114
+ return unless description && !description.empty?
115
+
116
+
117
+ if definition[:service] == :ec2
118
+ clear_out_ec2_security_group(name, description)
119
+ elsif definition[:service] == :rds
120
+ clear_out_rds_security_group(name, description)
121
+ end
122
+ end
123
+
124
+ def set_security_group(name, definition)
125
+ info " adding security rules to #{name}"
126
+ service = definition[:service]
127
+
128
+ definition[:rules].each { |rule|
129
+ rule.port ||= 0
130
+ rule.port_from ||= (rule.port)
131
+ rule.port_to ||= (rule.port)
132
+
133
+ protocol_description = service == :ec2 ? "#{rule.protocol}:#{rule.port_from}-#{rule.port_to}" : ""
134
+
135
+ if rule.group
136
+ info " allow from #{rule.owner_id}/#{rule.group_id} (#{rule.group}) #{protocol_description}"
137
+ if service == :ec2
138
+ authorize_ec2_security_group_rule(definition[:group_id], rule.port_from, rule.port_to, rule.protocol,
139
+ :groups => {rule.owner_id => rule.group_id})
140
+ else
141
+ safe_authorize_rds_security_group_rule(definition[:group_id],
142
+ :ec2_security_group_owner => rule.owner_id,
143
+ :ec2_security_group_name => rule.group_name)
144
+ end
145
+ elsif rule.role
146
+ info " allow from #{rule.owner_id}/#{rule.group_id} (role '#{rule.role}') #{protocol_description}"
147
+ if service == :ec2
148
+ authorize_ec2_security_group_rule(definition[:group_id], rule.port_from, rule.port_to, rule.protocol,
149
+ :groups => {rule.owner_id => rule.group_id})
150
+ else
151
+ safe_authorize_rds_security_group_rule(definition[:group_id],
152
+ :ec2_security_group_owner => rule.owner_id,
153
+ :ec2_security_group_name => rule.group_id)
154
+ end
155
+ elsif rule.cidr
156
+ info " allow from #{rule.cidr.inspect} #{protocol_description}"
157
+ if service == :ec2
158
+ authorize_ec2_security_group_rule(definition[:group_id], rule.port_from, rule.port_to, rule.protocol,
159
+ :cidr_ips => [rule.cidr].flatten)
160
+ else
161
+ [rule.cidr].flatten.each { |cidr|
162
+ safe_authorize_rds_security_group_rule(definition[:group_id], :cidrip => cidr)
163
+ }
164
+ end
165
+ end
166
+ }
167
+ end
168
+
169
+ ### EC2 ###
170
+
171
+ def clear_out_ec2_security_group(name, description)
172
+ description[:aws_perms].each { |permission|
173
+ group_id = description[:group_id]
174
+
175
+ revoke_params = if permission[:group_id]
176
+ # this is a group rule
177
+ {:groups => {permission[:owner] => permission[:group_id]}}
178
+ else
179
+ {:cidr_ip => permission[:cidr_ips]}
180
+ end
181
+
182
+ revoke_params[:protocol] = permission[:protocol]
183
+ revoke_params[:from_port] = permission[:from_port]
184
+ revoke_params[:to_port] = permission[:to_port]
185
+
186
+ info " revoking #{revoke_params.inspect}"
187
+ connection.ec2.modify_security_group(:revoke, :ingress,
188
+ group_id, revoke_params);
189
+ }
190
+ end
191
+
192
+ def authorize_ec2_security_group_rule(group_id, from_port, to_port, protocol, rule)
193
+ params = {:from_port => from_port,
194
+ :to_port => to_port,
195
+ :protocol => protocol}.merge(rule)
196
+ connection.ec2.modify_security_group(:authorize, :ingress, group_id, params)
197
+ end
198
+
199
+
200
+
201
+ ### RDS ###
202
+
203
+ def clear_out_rds_security_group(name, description)
204
+ description[:ec2_security_groups].each {|group_permission|
205
+ info " revoking from #{group_permission[:owner_id]}/#{group_permission[:name]}"
206
+ safe_revoke_rds_security_group(name,
207
+ :ec2_security_group_owner => group_permission[:owner_id],
208
+ :ec2_security_group_name => group_permission[:name])
209
+ }
210
+
211
+ description[:ip_ranges].each {|ip_permission|
212
+ info " revoking from #{ip_permission[:cidrip].inspect}"
213
+ safe_revoke_rds_security_group(name, :cidrip => ip_permission[:cidrip])
214
+ }
215
+ end
216
+
217
+
218
+ def update_definitions_rules_with_real_ids(definitions)
219
+ definitions.each { |name, definition|
220
+ definition[:rules].each { |rule|
221
+ next if rule.cidr
222
+
223
+ owner_id, group_id = if rule.role
224
+ resolve_owner_and_group_id_for_role_definition(rule.role)
225
+ elsif rule.group
226
+ resolve_owner_and_group_id_for_group_definition(rule.group)
227
+ end
228
+
229
+ unless owner_id && group_id
230
+ raise BadSecurityRule.new("Can't resolve security group id and owner for rule #{rule.inspect}}")
231
+ else
232
+ rule.owner_id = owner_id
233
+ rule.group_id = group_id
234
+ end
235
+ }
236
+ }
237
+
238
+ end
239
+
240
+
241
+ def resolve_owner_and_group_id_for_role_definition(role_name)
242
+ definition = @security_group_definitions.values.find {|definition| definition[:role] == role_name}
243
+ if definition
244
+ p [definition[:owner_id], definition[:group_id]]
245
+ [definition[:owner_id], definition[:group_id]]
246
+ else
247
+ security_group_name = "#{@config.profile.name}-#{role_name}"
248
+ raise BadSecurityRule.new("Can't find security group '#{security_group_name}' for role '#{role_name}'")
249
+ end
250
+ end
251
+
252
+ def resolve_owner_and_group_id_for_group_definition(group_definition)
253
+ # 'owner_id/group_name' => ['owner_id', 'group_name'] OR 'group_name' => ['group_name']
254
+ owner_id_and_group = group_definition.split('/')
255
+
256
+ group = owner_id_and_group.pop
257
+ owner_id = owner_id_and_group.pop
258
+
259
+ group_id = if group =~ /sg-/
260
+ group
261
+ else
262
+ # look up name
263
+ definition = @security_group_definitions.values.find {|definition| definition[:group_name] == group}
264
+
265
+ definition[:group_id] if definition
266
+ end
267
+
268
+ raise BadSecurityRule.new("Can't find group id (sg-...) for group name #{group}") unless group_id
269
+
270
+ # use this account for owner id
271
+ owner_id = @security_group_definitions.values.first[:owner_id] unless owner_id
272
+
273
+ [owner_id, group_id]
274
+ end
275
+
276
+
277
+ def safe_revoke_rds_security_group(name, params)
278
+ rds_logger = connection.rds.logger
279
+ connection.rds.logger = NullLogger.new
280
+
281
+ tries = 4
282
+
283
+ loop do
284
+ if tries <= 0
285
+ info "!!!!!! FAILED TO REVOKE: #{name} #{params.inspect} !!!!!!"
286
+ return
287
+ end
288
+
289
+ begin
290
+ connection.rds.revoke_db_security_group_ingress(name, params)
291
+ info " (succesfully revoked)"
292
+ return
293
+ rescue Exception => e
294
+ if e.message =~ /AuthorizationNotFound/
295
+ info " (not authorized. nothing to do here)"
296
+ return
297
+
298
+ elsif e.message =~ /InvalidDBSecurityGroupState: Cannot revoke an authorization that is in the revoking state/
299
+ info " (already revoking - will be revoked shortly)"
300
+ sleep 10
301
+ tries -= 1
302
+
303
+ elsif e.message =~ /InvalidDBSecurityGroupState: Cannot revoke an authorization that is in the authorizing state/
304
+ info " (not yet finished authorizing. waiting and retrying...)"
305
+ sleep 10
306
+ tries -= 1
307
+
308
+ else
309
+ sleep 1
310
+ tries -= 1
311
+ end
312
+ end
313
+ end
314
+ ensure
315
+ connection.rds.logger = rds_logger
316
+ end
317
+
318
+
319
+ def safe_authorize_rds_security_group_rule(name, params)
320
+ rds_logger = connection.rds.logger
321
+ connection.rds.logger = NullLogger.new
322
+
323
+ tries = 4
324
+
325
+ loop do
326
+ if tries <= 0
327
+ info "!!!!!! FAILED TO AUTHORIZE: #{name} #{params.inspect} !!!!!!"
328
+ return
329
+ end
330
+
331
+ begin
332
+ connection.rds.authorize_db_security_group_ingress(name, params)
333
+ info " (succesfully authorized)"
334
+ return
335
+ rescue Exception => e
336
+ if e.message =~ /AuthorizationAlreadyExists/
337
+ info " (authorization already exists. probably means it is still being revoked. retrying...)"
338
+ sleep 10
339
+ tries -= 1
340
+
341
+ elsif e.message =~ /InvalidDBSecurityGroupState: Cannot authorize an authorization that is in the revoking state/
342
+ info " (currently revoking - will be revoked shortly. will retry authorizing then)"
343
+ sleep 10
344
+ tries -= 1
345
+
346
+ elsif e.message =~ /InvalidDBSecurityGroupState: Cannot authorize an authorization that is in the authorizing state/
347
+ info " (already authorizing. waiting and retrying to confirm...)"
348
+ sleep 10
349
+ tries -= 1
350
+
351
+ else
352
+ sleep 1
353
+ tries -= 1
354
+ end
355
+ end
356
+ end
357
+ ensure
358
+ connection.rds.logger = rds_logger
359
+ end
360
+
361
+
362
+ ##### DESCRIPTIONS ######
363
+
364
+ ### EC2 ###
365
+
366
+ # ec2.describe_security_groups #=>
367
+ # [{:aws_perms=>
368
+ # [{:owner=>"375390957666",
369
+ # :direction=>:ingress,
370
+ # :protocol=>"tcp",
371
+ # :group_id=>"sg-d7e9d9be",
372
+ # :from_port=>"22",
373
+ # :group_name=>"foo-e2e-queue",
374
+ # :to_port=>"22"},
375
+
376
+ # {:cidr_ips=>"208.240.243.170/32",
377
+ # :direction=>:ingress,
378
+ # :protocol=>"tcp",
379
+ # :from_port=>"22",
380
+ # :to_port=>"22"}],
381
+
382
+ # {:owner=>"375390957666",
383
+ # :direction=>:ingress,
384
+ # :protocol=>"icmp",
385
+ # :group_id=>"sg-9f5d5cf6",
386
+ # :from_port=>"15",
387
+ # :group_name=>"foo-e2e-service",
388
+ # :to_port=>"-1"}],
389
+
390
+ # :aws_owner=>"375390957666",
391
+ # :aws_description=>"something here",
392
+ # :group_id=>"sg-30fd1b58",
393
+ # :aws_group_name=>"test2"}]
394
+
395
+ # connection.ec2.modify_security_group(:grant, :ingress,
396
+ # 'sg-30fd1b58', {:protocol => 'tcp', :port => 22, :cidr_ip => '127.0.0.2/32'})
397
+ #
398
+ # connection.ec2.modify_security_group(:revoke, :ingress,
399
+ # 'sg-30fd1b58', {:protocol => :tcp, :port => 22, :groups => {'375390957666' => 'sg-d7e9d9be'} });
400
+
401
+
402
+
403
+ ### RDS ###
404
+
405
+ # rds.describe_db_security_groups #=>
406
+ # [{:owner_id=>"375390957666",
407
+ # :description=>"Default",
408
+ # :ec2_security_groups=>[],
409
+ # :ip_ranges=>[],
410
+ # :name=>"Default"},
411
+ # {:owner_id=>"375390957666",
412
+ # :description=>"kd",
413
+ # :ec2_security_groups=>[],
414
+ # :ip_ranges=>[],
415
+ # :name=>"kd2"},
416
+ # {:owner_id=>"375390957666",
417
+ # :description=>"kd",
418
+ # :ec2_security_groups=>
419
+ # [{:status=>"Authorized", :owner_id=>"375390957666", :name=>"default"},
420
+ # {:status=>"Authorized", :owner_id=>"375390957666", :name=>"default1"},
421
+ # {:status=>"Authorized", :owner_id=>"375390957666", :name=>"default"},
422
+ # {:status=>"Authorized", :owner_id=>"375390957666", :name=>"default"},
423
+ # {:status=>"Authorized", :owner_id=>"375390957666", :name=>"default1"},
424
+ # {:status=>"Authorized", :owner_id=>"375390957666", :name=>"default22"}],
425
+ # :ip_ranges=>
426
+ # [{:status=>"Authorized", :cidrip=>"127.0.0.1/8"},
427
+ # {:status=>"Authorized", :cidrip=>"128.0.0.1/8"},
428
+ # {:status=>"Authorized", :cidrip=>"129.0.0.1/8"},
429
+ # {:status=>"Authorized", :cidrip=>"130.0.0.1/8"},
430
+ # {:status=>"Authorized", :cidrip=>"131.0.0.1/8"}],
431
+ # :name=>"kd3"}]
432
+
433
+ # rds.revoke_db_security_group_ingress('kd2', :ec2_security_group_owner => '375390957666',
434
+ # :ec2_security_group_name => 'default')
435
+ #
436
+ # rds.revoke_db_security_group_ingress('kd2', :cidrip=>"127.0.0.1/8")
437
+ #
438
+ # rds.authorize_db_security_group_ingress('kd3', :cidrip => '131.0.0.1/8')
439
+ end
440
+
441
+ class BadSecurityRule < Exception
442
+ end
@@ -0,0 +1,11 @@
1
+ require 'maws/command'
2
+
3
+ class Start < Command
4
+ def description
5
+ "start - start specified EC2 instances that were stopped"
6
+ end
7
+
8
+ def run!
9
+ instances.specified.each {|i| i.start if i.respond_to?(:start)}
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ require 'maws/command'
2
+ require 'terminal-table/import'
3
+
4
+ class Status < Command
5
+ def description
6
+ "status - show information about specified instances"
7
+ end
8
+
9
+ def run!
10
+ instances.specified
11
+ if instances.specified.empty?
12
+ info "no instances specified"
13
+ return
14
+ end
15
+
16
+ roles_list = @config.available_roles
17
+
18
+ instances.specified.roles_in_order_of(roles_list).each { |role_name|
19
+ role_instances = instances.specified.with_role(role_name)
20
+ next if role_instances.empty?
21
+ InstanceDisplay.display_collection_for_role(role_name, role_instances)
22
+ }
23
+
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ require 'maws/command'
2
+
3
+ class Stop < Command
4
+ def description
5
+ "stop - stop specified EC2 instances that are running"
6
+ end
7
+
8
+ def run!
9
+ instances.specified.each {|i| i.stop if i.respond_to?(:stop)}
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ require 'maws/command'
2
+
3
+ class Teardown < Command
4
+ def description
5
+ "teardown - destroys all instances that are not specified, but exist on AWS"
6
+ end
7
+
8
+ def run!
9
+ instances.aws.not_specified.alive.each {|i| i.destroy}
10
+ end
11
+ end
@@ -0,0 +1,22 @@
1
+ require 'maws/command'
2
+ require 'maws/volumes_command'
3
+ require 'terminal-table/import'
4
+
5
+ class VolumesCleanup < VolumesCommand
6
+ def description
7
+ "volumes-cleaned - delete unattached EBS volumes for specified roles"
8
+ end
9
+
10
+ def run!
11
+ super
12
+
13
+ unattached = instances.specified.ebs.matching(:attached? => false)
14
+
15
+ if unattached.empty?
16
+ info "no unattached volumes to clean up"
17
+ return
18
+ end
19
+
20
+ unattached.each {|i| i.destroy}
21
+ end
22
+ end
@@ -0,0 +1,43 @@
1
+ require 'maws/command'
2
+ require 'maws/volumes_command'
3
+ require 'terminal-table/import'
4
+
5
+ class VolumesStatus < VolumesCommand
6
+ def description
7
+ "volumes-status - show brief status information for EBS volumes for specified roles"
8
+ end
9
+
10
+ def run!
11
+ super
12
+ attached = instances.specified.ebs.matching(:attached? => true)
13
+ unattached = instances.specified.ebs.matching(:attached? => false)
14
+
15
+ info "\n**** " + "ATTACHED EBS VOLUMES" + " *****************"
16
+ list_ebs_instances(attached)
17
+
18
+ info "\n\n**** " + "UNATTACHED EBS VOLUMES" + " *****************"
19
+ list_ebs_instances(unattached)
20
+ end
21
+
22
+ def list_ebs_instances(instances)
23
+ if instances.empty?
24
+ info "none available"
25
+ return
26
+ end
27
+
28
+ headers = instances.first.display.headers
29
+ table_rows = []
30
+ grouped_instances = instances.
31
+ group_by{|i| i.name}.
32
+ to_a.sort_by {|group| group[0]} # sort by name
33
+
34
+ grouped_instances.each_with_index do |(name, instances), i|
35
+ instances.each {|instance|
36
+ table_rows << instance.display.values
37
+ }
38
+
39
+ end
40
+
41
+ info table(headers, *table_rows)
42
+ end
43
+ end
@@ -0,0 +1,61 @@
1
+ require 'maws/command'
2
+ require 'maws/trollop'
3
+
4
+ class Wait < Command
5
+ def description
6
+ "wait - do nothing until specified instances enter specified state, then quite and notify"
7
+ end
8
+
9
+ def add_specific_options(parser)
10
+ parser.opt :target_state, "State to wait for", :type => :string
11
+ parser.opt :wait, "Max wait (seconds)", :default => 60
12
+ parser.opt :count, "Minimum number of specified instances that must match the state", :default => 0
13
+ parser.opt :quiet, "Be quiet", :type => :flag, :default => false
14
+ parser.opt :growl, "Growl notify when done", :type => :flag, :default => false
15
+ end
16
+
17
+ def verify_options
18
+ super
19
+ state = @config.command_line.target_state
20
+ Trollop::die "Can't wait for blank state" if state.nil? || state.empty?
21
+ end
22
+
23
+ def run!
24
+ at_exit do
25
+ if @config.command_line.growl
26
+ system("growlnotify -m 'waiting for AWS state #{@config.command_line.target_state} done'")
27
+ end
28
+ end
29
+
30
+ state = @config.command_line.target_state
31
+ been_waiting = 0
32
+ wait_for_count = @config.command_line.count == 0 ? instances.specified.count : @config.command_line.count
33
+ wait_for_time = @config.command_line.wait
34
+
35
+ info "waiting #{wait_for_time} seconds or until #{wait_for_count} are #{state}..."
36
+
37
+ loop do
38
+ trap("INT") { info "...done (interrupted)"; return }
39
+ return if been_waiting >= wait_for_time
40
+ left_to_wait = wait_for_time - been_waiting
41
+ matching_count = instances.specified.with_approximate_status(state).count
42
+
43
+ if matching_count >= wait_for_count
44
+ info "...done (#{matching_count}/#{wait_for_count} are #{state})"
45
+ return
46
+ end
47
+
48
+ if @config.command_line.quiet
49
+ print "."
50
+ $stdout.flush
51
+ else
52
+ info "#{matching_count}/#{wait_for_count} are #{state} - wait #{left_to_wait} seconds or until #{wait_for_count}/#{wait_for_count} are #{state}..."
53
+ end
54
+
55
+
56
+ sleep 1
57
+ been_waiting += 1
58
+ @maws.resync_instances
59
+ end
60
+ end
61
+ end