maws 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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