jnewland-capsize 0.5.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.
@@ -0,0 +1,568 @@
1
+ module Capsize
2
+ module CapsizeEC2
3
+ include Capsize
4
+
5
+
6
+ # HELPER METHODS
7
+ #########################################
8
+
9
+
10
+ def hostname_from_instance_id(instance_id = nil)
11
+ raise Exception, "Instance ID required" if instance_id.nil? || instance_id.empty?
12
+
13
+ amazon = connect()
14
+
15
+ response = amazon.describe_instances(:instance_id => instance_id)
16
+ return dns_name = response.reservationSet.item[0].instancesSet.item[0].dnsName
17
+ end
18
+
19
+ def hostnames_from_instance_ids(ids = [])
20
+ ids.collect { |id| hostname_from_instance_id(id) }
21
+ end
22
+
23
+ def hostnames_from_group(group_name = nil)
24
+ hostnames = []
25
+ return hostnames if group_name.nil?
26
+ instances = describe_instances
27
+ return hostnames if instances.nil?
28
+ return hostnames if instances.reservationSet.nil?
29
+ instances.reservationSet.item.each do |reservation|
30
+ hostname = nil
31
+ in_group = false
32
+ running = false
33
+ unless reservation.groupSet.nil?
34
+ reservation.groupSet.item.each do |group|
35
+ in_group = group.groupId == group_name
36
+ end
37
+ end
38
+
39
+ unless reservation.instancesSet.nil?
40
+ reservation.instancesSet.item.each do |instance|
41
+ hostname = instance.dnsName
42
+ running = (!instance.instanceState.nil? && (instance.instanceState.name == "running"))
43
+ end
44
+ end
45
+ hostnames << hostname if in_group and running
46
+ end
47
+ return hostnames
48
+ end
49
+
50
+ def role_from_security_group(role, security_group, *args)
51
+ options = args.last.is_a?(Hash) ? args.pop : {}
52
+ options = {:user => 'root', :ssh_options => { :keys => [capsize_ec2.get_key_file] }}.merge(options)
53
+ role(role, options) do
54
+ hostnames_from_group(security_group)
55
+ end
56
+ end
57
+
58
+ # build the key file path from key_dir and key_file
59
+ def get_key_file(options = {})
60
+ options = {:key_dir => nil, :key_name => nil}.merge(options)
61
+ key_dir = options[:key_dir] || get(:key_dir) || get(:capsize_secure_config_dir)
62
+ key_name = options[:key_name] || get(:key_name)
63
+ return key_file = [key_dir, "id_rsa-" + key_name].join('/')
64
+ end
65
+
66
+
67
+ # CONSOLE METHODS
68
+ #########################################
69
+
70
+
71
+ def get_console_output(options = {})
72
+ amazon = connect()
73
+ options = {:instance_id => ""}.merge(options)
74
+ amazon.get_console_output(:instance_id => options[:instance_id])
75
+ end
76
+
77
+
78
+ # KEYPAIR METHODS
79
+ #########################################
80
+
81
+
82
+ #describe your keypairs
83
+ def describe_keypairs(options = {})
84
+ amazon = connect()
85
+ options = {:key_name => []}.merge(options)
86
+ amazon.describe_keypairs(:key_name => options[:key_name])
87
+ end
88
+
89
+ #sets up a keypair named options[:key_name] and writes out the private key to options[:key_dir]
90
+ def create_keypair(options = {})
91
+ amazon = connect()
92
+
93
+ # default key_name is the same as our appname, unless specifically overriden in capsize.yml
94
+ # default key_dir is set in the :capsize_config_dir variable
95
+ options = {:key_name => nil, :key_dir => nil}.merge(options)
96
+
97
+ options[:key_name] = options[:key_name] || get(:key_name)
98
+ options[:key_dir] = options[:key_dir] || get(:key_dir) || get(:capsize_secure_config_dir)
99
+
100
+ #verify key_name and key_dir are set
101
+ raise Exception, "Keypair name required" if options[:key_name].nil? || options[:key_name].empty?
102
+ raise Exception, "Keypair directory required" if options[:key_dir].nil? || options[:key_dir].empty?
103
+
104
+ key_file = get_key_file(:key_name => options[:key_name], :key_dir => options[:key_dir])
105
+
106
+ # Verify keypair doesn't already exist on EC2 servers...
107
+ unless amazon.describe_keypairs(:key_name => options[:key_name]).keySet.nil?
108
+ raise Exception, "Sorry, a keypair with the name \"#{options[:key_name]}\" already exists on EC2."
109
+ end
110
+
111
+ # and doesn't exist locally either...
112
+ file_exists_message = <<-MESSAGE
113
+ \n
114
+ Warning! A keypair with the name \"#{key_file}\"
115
+ already exists on your local filesytem. You must remove it before trying to overwrite
116
+ again. Warning! Removing keypairs associated with active instances will prevent you
117
+ from accessing them via SSH or Capistrano!!\n\n
118
+ MESSAGE
119
+ raise Exception, file_exists_message if File.exists?(key_file)
120
+
121
+ #All is good, so we create the new keypair
122
+ private_key = amazon.create_keypair(:key_name => options[:key_name])
123
+
124
+ # write private key to file
125
+ File.open(key_file, 'w') do |file|
126
+ file.write(private_key.keyMaterial)
127
+ end
128
+
129
+ # Cross platform CHMOD, make the file owner +rw, group and other -all
130
+ File.chmod 0600, key_file
131
+ return [key_name, key_file]
132
+ end
133
+
134
+
135
+ # TODO : Is there a way to extract the 'puts' calls from here and make this have less 'view' code?
136
+ # Deletes a keypair from EC2 and from the local filesystem
137
+ def delete_keypair(options = {})
138
+ amazon = connect()
139
+
140
+ options = {:key_name => nil, :key_dir => nil}.merge(options)
141
+
142
+ options[:key_name] = options[:key_name] || get(:key_name)
143
+ options[:key_dir] = options[:key_dir] || get(:key_dir) || get(:capsize_secure_config_dir)
144
+
145
+ raise Exception, "Keypair name required" if options[:key_name].nil? || options[:key_name].empty?
146
+ raise Exception, "Keypair directory required" if options[:key_dir].nil? || options[:key_dir].empty?
147
+ raise Exception, "Keypair \"#{options[:key_name]}\" does not exist on EC2." if amazon.describe_keypairs(:key_name => options[:key_name]).keySet.nil?
148
+
149
+ # delete the keypair from the amazon EC2 servers
150
+ amazon.delete_keypair(:key_name => options[:key_name])
151
+ puts "Keypair \"#{options[:key_name]}\" deleted from EC2!"
152
+
153
+ begin
154
+ # determine the local key file name and delete it
155
+ key_file = get_key_file(:key_name => options[:key_name])
156
+ File.delete(key_file)
157
+ rescue
158
+ puts "Keypair \"#{key_file}\" not found on the local filesystem."
159
+ else
160
+ puts "Keypair \"#{key_file}\" deleted from local file system!"
161
+ end
162
+ end
163
+
164
+
165
+ # IMAGE METHODS
166
+ #########################################
167
+
168
+
169
+ #describe the amazon machine images available for launch
170
+ # Even though the amazon-ec2 library allows us to pass in an array of image_id's,
171
+ # owner_id's, or executable_by's we restrict Capsize usage to passing in a String
172
+ # with a single value.
173
+ def describe_images(options = {})
174
+ amazon = connect()
175
+
176
+ options = {:image_id => nil, :owner_id => nil, :executable_by => nil}.merge(options)
177
+
178
+ options[:image_id] = options[:image_id] || get(:image_id) || ""
179
+ options[:owner_id] = options[:owner_id] || get(:owner_id) || ""
180
+ options[:executable_by] = options[:executable_by] || get(:executable_by) || ""
181
+
182
+ amazon.describe_images(:image_id => options[:image_id], :owner_id => options[:owner_id], :executable_by => options[:executable_by])
183
+
184
+ end
185
+
186
+
187
+ # INSTANCE METHODS
188
+ #########################################
189
+
190
+
191
+ #returns information about instances owned by the user
192
+ def describe_instances(options = {})
193
+ amazon = connect()
194
+ options = {:instance_id => []}.merge(options)
195
+ amazon.describe_instances(:instance_id => options[:instance_id])
196
+ end
197
+
198
+
199
+ # Run EC2 instance(s)
200
+ # TODO : Deal with starting multiple instances! Now only single instances are properly handled.
201
+ def run_instance(options = {})
202
+ amazon = connect()
203
+
204
+ options = { :image_id => get(:image_id),
205
+ :min_count => get(:min_count),
206
+ :max_count => get(:max_count),
207
+ :key_name => nil,
208
+ :group_name => nil,
209
+ :user_data => get(:user_data),
210
+ :addressing_type => get(:addressing_type),
211
+ :instance_type => get(:instance_type)
212
+ }.merge(options)
213
+
214
+ # What security group should we run as?
215
+ options[:group_id] = (options[:group_name] || get(:group_name) || "").split(',')
216
+
217
+ # We want to run the new instance using our public/private keypair if
218
+ # one is defined for this application or of the user has explicitly passed
219
+ # in a key_name as a parameter. Only allow use of application name keyname if
220
+ # the <application> name is defined on EC2 as a key_name, AND we have the local
221
+ # private key stored in the config dir.
222
+
223
+ # override application key_name if the user provided one in config or on the command line
224
+ options[:key_name] = options[:key_name] || get(:key_name)
225
+
226
+ # key_dir defaults to same as :capsize_config_dir variable
227
+ options[:key_dir] = options[:key_dir] || get(:key_dir) || get(:capsize_secure_config_dir)
228
+
229
+ # determine the local key file name and delete it
230
+ key_file = get_key_file(:key_name => options[:key_name], :key_dir => options[:key_dir])
231
+
232
+ # don't let them go further if there is no private key present.
233
+ raise Exception, "Private key is not present in #{key_file}.\nPlease generate one with 'cap ec2:keypairs:create' or specify a different KEY_NAME." unless File.exists?(key_file)
234
+
235
+ # Verify image_id, min_count, and max_count are present as these are required
236
+ raise Exception, "image_id (ami-) required" if options[:image_id].nil? || options[:image_id].empty?
237
+ raise Exception, "min_count is required" if options[:min_count].nil?
238
+ raise Exception, "max_count is required" if options[:max_count].nil?
239
+
240
+ # Start instance(s)!
241
+ response = amazon.run_instances(options)
242
+
243
+ instance_id = response.instancesSet.item[0].instanceId
244
+ puts "Instance #{instance_id} startup in progress"
245
+
246
+ #set scope outside of block
247
+ instance = nil
248
+
249
+ #loop checking for confirmation that instance is running
250
+ tries = 0
251
+ begin
252
+ instance = amazon.describe_instances(:instance_id => instance_id)
253
+ raise "Server Not Running" unless instance.reservationSet.item[0].instancesSet.item[0].instanceState.name == "running"
254
+ puts ""
255
+ puts "Instance #{instance_id} entered state 'running'"
256
+ rescue
257
+ $stdout.print '.'
258
+ sleep(10)
259
+ tries += 1
260
+ retry unless tries == 35
261
+ raise "Instance #{instance_id} never moved to state 'running'!"
262
+ end
263
+
264
+ #loop waiting to get the public key
265
+ tries = 0
266
+ begin
267
+ require 'timeout'
268
+ begin
269
+ Timeout::timeout(5) do
270
+ system("ssh -o StrictHostKeyChecking=no -o PasswordAuthentication=no -i #{get_key_file} root@#{hostname_from_instance_id(instance_id)} echo success") or raise "SSH Auth Failure"
271
+ end
272
+ rescue Timeout::Error
273
+ raise "SSH timed out..."
274
+ end
275
+ puts ""
276
+ puts "SSH is up! Grabbing the public key..."
277
+ if system "scp -o StrictHostKeyChecking=no -i #{get_key_file} root@#{hostname_from_instance_id(instance_id)}:/mnt/openssh_id.pub #{get_key_file}.pub"
278
+ puts "Public key saved at #{get_key_file}.pub"
279
+ else
280
+ puts "Error grabbing public key"
281
+ end
282
+ rescue Exception => e
283
+ $stdout.print '.'
284
+ sleep(10)
285
+ tries += 1
286
+ retry unless tries == 35
287
+ puts "We couldn't ever SSH in!"
288
+ end
289
+
290
+ #scripts
291
+ if File.exists?(fetch(:capsize_config_dir)+"/scripts")
292
+ begin
293
+ instance = amazon.describe_instances(:instance_id => instance_id)
294
+ instance.reservationSet.item.first.groupSet.item.map { |g| g.groupId }.sort.each do |group|
295
+ script_path = fetch(:capsize_config_dir)+"/scripts/#{group}"
296
+ if File.exists?(script_path)
297
+ begin
298
+ puts "Found script for security group #{group}, running"
299
+ system("scp -o StrictHostKeyChecking=no -i #{get_key_file} #{script_path} root@#{hostname_from_instance_id(instance_id)}:/tmp/") or raise "SCP ERROR"
300
+ system("ssh -o StrictHostKeyChecking=no -i #{get_key_file} root@#{hostname_from_instance_id(instance_id)} chmod o+x /tmp/#{group}") or raise "Error changing script permissions"
301
+ system("ssh -o StrictHostKeyChecking=no -i #{get_key_file} root@#{hostname_from_instance_id(instance_id)} /tmp/#{group}") or raise "Error running script"
302
+ rescue Exception => e
303
+ puts e
304
+ end
305
+ end
306
+ end
307
+ rescue Exception => e
308
+ puts e
309
+ end
310
+ end
311
+
312
+ return instance
313
+ end
314
+
315
+
316
+ #reboot a running instance
317
+ def reboot_instance(options = {})
318
+ amazon = connect()
319
+ options = {:instance_id => []}.merge(options)
320
+ raise Exception, ":instance_id required" if options[:instance_id].nil?
321
+ amazon.reboot_instances(:instance_id => options[:instance_id])
322
+ end
323
+
324
+
325
+ #terminates a running instance
326
+ def terminate_instance(options = {})
327
+ amazon = connect()
328
+ options = {:instance_id => []}.merge(options)
329
+ raise Exception, ":instance_id required" if options[:instance_id].nil?
330
+ amazon.terminate_instances(:instance_id => options[:instance_id])
331
+ end
332
+
333
+
334
+ # SECURITY GROUP METHODS
335
+ #########################################
336
+
337
+
338
+ def create_security_group(options = {})
339
+ amazon = connect()
340
+
341
+ # default group_name is the same as our appname, unless specifically overriden in capsize.yml
342
+ # default group_description is set in the :group_description variable
343
+ options = {:group_name => nil, :group_description => nil}.merge(options)
344
+
345
+ options[:group_name] = options[:group_name] || get(:group_name)
346
+ options[:group_description] = options[:group_description] || get(:group_description)
347
+
348
+ raise Exception, "Group name required" if options[:group_name].nil? || options[:group_name].empty?
349
+ raise Exception, "Group description required" if options[:group_description].nil? || options[:group_description].empty?
350
+
351
+ amazon.create_security_group(:group_name => options[:group_name], :group_description => options[:group_description])
352
+
353
+ end
354
+
355
+
356
+ #describe your security groups
357
+ def describe_security_groups(options = {})
358
+ amazon = connect()
359
+ options = {:group_name => nil}.merge(options)
360
+ options[:group_name] = options[:group_name] || get(:group_name) || ""
361
+ amazon.describe_security_groups(:group_name => options[:group_name])
362
+ end
363
+
364
+
365
+ def delete_security_group(options = {})
366
+ amazon = connect()
367
+
368
+ # default group_name is the same as our appname, unless specifically overriden in capsize.yml
369
+ options = {:group_name => nil}.merge(options)
370
+
371
+ options[:group_name] = options[:group_name] || get(:group_name)
372
+
373
+ raise Exception, "Group name required" if options[:group_name].nil? || options[:group_name].empty?
374
+
375
+ amazon.delete_security_group(:group_name => options[:group_name])
376
+
377
+ end
378
+
379
+
380
+ # Define firewall access rules for a specific security group. Instances will inherit
381
+ # the security group permissions based on the group they are assigned to.
382
+ def authorize_ingress(options = {})
383
+ amazon = connect()
384
+
385
+ options = { :group_name => nil,
386
+ :ip_protocol => get(:ip_protocol),
387
+ :from_port => get(:from_port),
388
+ :to_port => get(:to_port),
389
+ :cidr_ip => get(:cidr_ip),
390
+ :source_security_group_name => get(:source_security_group_name),
391
+ :source_security_group_owner_id => get(:source_security_group_owner_id) }.merge(options)
392
+
393
+ options[:group_name] = options[:group_name] || get(:group_name)
394
+
395
+ # Verify only that :group_name is passed. This is the only REQUIRED parameter.
396
+ # The others are optional and depend on what it is you are trying to
397
+ # do (CIDR based permissions vs. user/group pair permissions). We let the EC2
398
+ # service itself do the validations on the extra params and count on it to raise an exception
399
+ # if it doesn't like the options passed. We'll see an EC2::Exception class returned if so.
400
+ raise Exception, "You must specify a :group_name" if options[:group_name].nil? || options[:group_name].empty?
401
+
402
+ # set the :to_port to the same value as :from_port if :to_port was not explicitly defined.
403
+ unless options[:from_port].nil? || options[:from_port].empty?
404
+ set :to_port, options[:from_port] if options[:to_port].nil? || options[:to_port].empty?
405
+ options[:to_port] = to_port if options[:to_port].nil? || options[:to_port].empty?
406
+ end
407
+
408
+ #if source_security_group_name and source_security_group_owner_id are specified, unset the incompatible options
409
+ if !options[:source_security_group_name].nil? && !options[:source_security_group_owner_id].nil?
410
+ options.delete(:ip_protocol)
411
+ options.delete(:from_port)
412
+ options.delete(:to_port)
413
+ options.delete(:cidr_ip)
414
+ end
415
+
416
+ amazon.authorize_security_group_ingress(options)
417
+
418
+ end
419
+
420
+
421
+ # Revoke firewall access rules for a specific security group. Instances will inherit
422
+ # the security group permissions based on the group they are assigned to.
423
+ def revoke_ingress(options = {})
424
+ amazon = connect()
425
+
426
+ options = { :group_name => nil,
427
+ :ip_protocol => get(:ip_protocol),
428
+ :from_port => get(:from_port),
429
+ :to_port => get(:to_port),
430
+ :cidr_ip => get(:cidr_ip),
431
+ :source_security_group_name => get(:source_security_group_name),
432
+ :source_security_group_owner_id => get(:source_security_group_owner_id) }.merge(options)
433
+
434
+ options[:group_name] = options[:group_name] || get(:group_name)
435
+
436
+ # Verify only that :group_name is passed. This is the only REQUIRED parameter.
437
+ # The others are optional and depend on what it is you are trying to
438
+ # do (CIDR based permissions vs. user/group pair permissions). We let the EC2
439
+ # service itself do the validations on the extra params and count on it to raise an exception
440
+ # if it doesn't like the options passed. We'll see an EC2::Exception class returned if so.
441
+ raise Exception, "You must specify a :group_name" if options[:group_name].nil? || options[:group_name].empty?
442
+
443
+ # set the :to_port to the same value as :from_port if :to_port was not explicitly defined.
444
+ unless options[:from_port].nil? || options[:from_port].empty?
445
+ set :to_port, options[:from_port] if options[:to_port].nil? || options[:to_port].empty?
446
+ options[:to_port] = to_port if options[:to_port].nil? || options[:to_port].empty?
447
+ end
448
+
449
+ #if source_security_group_name and source_security_group_owner_id are specified, unset the incompatible options
450
+ if !options[:source_security_group_name].nil? && !options[:source_security_group_owner_id].nil?
451
+ options.delete(:ip_protocol)
452
+ options.delete(:from_port)
453
+ options.delete(:to_port)
454
+ options.delete(:cidr_ip)
455
+ end
456
+
457
+ amazon.revoke_security_group_ingress(options)
458
+
459
+ end
460
+
461
+ # CAPSIZE HELPER METHODS
462
+ #########################################
463
+ # call these from tasks.rb with 'capsize.method_name'
464
+ # returns an EC2::Base object
465
+ def connect()
466
+
467
+ # get the :use_ssl value from the config pool and set it if its available
468
+ # this will allow users to globally override whether or not their connection
469
+ # is made via SSL in their config files or deploy.rb. Of course default to using SSL.
470
+ case get(:use_ssl)
471
+ when true, nil
472
+ set :use_ssl, true
473
+ when false
474
+ set :use_ssl, false
475
+ else
476
+ raise Exception, "You have an invalid value in your config for :use_ssl. Must be 'true' or 'false'."
477
+ end
478
+
479
+ # Optimized so we don't read the config files six times just to connect.
480
+ # Read once, set it, and re-use what we get back...
481
+ set :aws_access_key_id, get(:aws_access_key_id)
482
+ set :aws_secret_access_key, get(:aws_secret_access_key)
483
+
484
+ raise Exception, "You must have an :aws_access_key_id defined in your config." if fetch(:aws_access_key_id).nil? || fetch(:aws_access_key_id).empty?
485
+ raise Exception, "You must have an :aws_secret_access_key defined in your config." if fetch(:aws_secret_access_key).nil? || fetch(:aws_secret_access_key).empty?
486
+
487
+ begin
488
+ return amazon = EC2::Base.new(:access_key_id => get(:aws_access_key_id), :secret_access_key => get(:aws_secret_access_key), :use_ssl => use_ssl)
489
+ rescue Exception => e
490
+ puts "Your EC2::Base authentication setup failed with the following message : " + e
491
+ raise e
492
+ end
493
+ end
494
+
495
+ # TODO : Finish this...
496
+ # accept a Response object and provide screen output of the key data from
497
+ # this response that needs to be permanently added to the users deploy.rb
498
+ # and/or Capsize config files.
499
+ def print_config_instructions(response = nil)
500
+
501
+ raise Exception, "run_instances Response object expected" if response.nil?
502
+
503
+ dns_name = response.reservationSet.item[0].instancesSet.item[0].dnsName
504
+
505
+ puts "\n\nConfiguration Instructions:\n"
506
+
507
+ config_help <<-HELP
508
+ In order to control this new server instance from Capsize and Capistrano in the
509
+ future you will need to store some critical instance information in your
510
+ deploy.rb configuration file. Please add something like the following to
511
+ the appropriate places in your config/deploy.rb file. Of course you may need to
512
+ modify this information to suite your circumstances, this is only an example.
513
+ \n\n
514
+ config/deploy.rb
515
+ --
516
+ HELP
517
+
518
+ puts config_help
519
+
520
+ puts "role :app, #{dns_name}"
521
+ puts "role :web, #{dns_name}"
522
+ puts "role :db, #{dns_name}, :primary => true"
523
+
524
+ end
525
+
526
+ # Keeping DRY. This is called from run instances and describe instances.
527
+ def print_instance_description(result = nil)
528
+ puts "" if result.nil?
529
+ unless result.reservationSet.nil?
530
+ result.reservationSet.item.each do |reservation|
531
+ puts "reservationSet:reservationId = " + reservation.reservationId
532
+ puts "reservationSet:ownerId = " + reservation.ownerId
533
+
534
+ unless reservation.groupSet.nil?
535
+ reservation.groupSet.item.each do |group|
536
+ puts " groupSet:groupId = " + group.groupId unless group.groupId.nil?
537
+ end
538
+ end
539
+
540
+ unless reservation.instancesSet.nil?
541
+ reservation.instancesSet.item.each do |instance|
542
+ puts " instancesSet:instanceId = " + instance.instanceId unless instance.instanceId.nil?
543
+ puts " instancesSet:instanceType = " + instance.instanceType unless instance.instanceType.nil?
544
+ puts " instancesSet:imageId = " + instance.imageId unless instance.imageId.nil?
545
+ puts " instancesSet:privateDnsName = " + instance.privateDnsName unless instance.privateDnsName.nil?
546
+ puts " instancesSet:dnsName = " + instance.dnsName unless instance.dnsName.nil?
547
+ puts " instancesSet:reason = " + instance.reason unless instance.reason.nil?
548
+ puts " instancesSet:launchTime = " + instance.launchTime unless instance.launchTime.nil?
549
+ puts " instancesSet:amiLaunchIndex = " + instance.amiLaunchIndex
550
+
551
+ unless instance.instanceState.nil?
552
+ puts " instanceState:code = " + instance.instanceState.code
553
+ puts " instanceState:name = " + instance.instanceState.name
554
+ end
555
+
556
+ end
557
+
558
+ end
559
+
560
+ puts ""
561
+ end
562
+ else
563
+ puts "You don't own any running or pending instances"
564
+ end
565
+ end
566
+ end
567
+ end
568
+ Capistrano.plugin :capsize_ec2, Capsize::CapsizeEC2