redaranj-right_aws 1.10.3

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,1837 @@
1
+ #
2
+ # Copyright (c) 2007-2008 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #
23
+
24
+ module RightAws
25
+
26
+ # = RightAWS::EC2 -- RightScale Amazon EC2 interface
27
+ # The RightAws::EC2 class provides a complete interface to Amazon's
28
+ # Elastic Compute Cloud service, as well as the associated EBS (Elastic Block
29
+ # Store).
30
+ # For explanations of the semantics
31
+ # of each call, please refer to Amazon's documentation at
32
+ # http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=87
33
+ #
34
+ # Examples:
35
+ #
36
+ # Create an EC2 interface handle:
37
+ #
38
+ # @ec2 = RightAws::Ec2.new(aws_access_key_id,
39
+ # aws_secret_access_key)
40
+ # Create a new SSH key pair:
41
+ # @key = 'right_ec2_awesome_test_key'
42
+ # new_key = @ec2.create_key_pair(@key)
43
+ # keys = @ec2.describe_key_pairs
44
+ #
45
+ # Create a security group:
46
+ # @group = 'right_ec2_awesome_test_security_group'
47
+ # @ec2.create_security_group(@group,'My awesome test group')
48
+ # group = @ec2.describe_security_groups([@group])[0]
49
+ #
50
+ # Configure a security group:
51
+ # @ec2.authorize_security_group_named_ingress(@group, account_number, 'default')
52
+ # @ec2.authorize_security_group_IP_ingress(@group, 80,80,'udp','192.168.1.0/8')
53
+ #
54
+ # Describe the available images:
55
+ # images = @ec2.describe_images
56
+ #
57
+ # Launch an instance:
58
+ # ec2.run_instances('ami-9a9e7bf3', 1, 1, ['default'], @key, 'SomeImportantUserData', 'public')
59
+ #
60
+ #
61
+ # Describe running instances:
62
+ # @ec2.describe_instances
63
+ #
64
+ # Error handling: all operations raise an RightAws::AwsError in case
65
+ # of problems. Note that transient errors are automatically retried.
66
+
67
+ class Ec2 < RightAwsBase
68
+ include RightAwsBaseInterface
69
+
70
+ # Amazon EC2 API version being used
71
+ API_VERSION = "2009-03-01"
72
+ DEFAULT_HOST = "ec2.amazonaws.com"
73
+ DEFAULT_PATH = '/'
74
+ DEFAULT_PROTOCOL = 'https'
75
+ DEFAULT_PORT = 443
76
+
77
+ # Default addressing type (public=NAT, direct=no-NAT) used when launching instances.
78
+ DEFAULT_ADDRESSING_TYPE = 'public'
79
+ DNS_ADDRESSING_SET = ['public','direct']
80
+
81
+ # Amazon EC2 Instance Types : http://www.amazon.com/b?ie=UTF8&node=370375011
82
+ # Default EC2 instance type (platform)
83
+ DEFAULT_INSTANCE_TYPE = 'm1.small'
84
+ INSTANCE_TYPES = ['m1.small','c1.medium','m1.large','m1.xlarge','c1.xlarge']
85
+
86
+ @@bench = AwsBenchmarkingBlock.new
87
+ def self.bench_xml
88
+ @@bench.xml
89
+ end
90
+ def self.bench_ec2
91
+ @@bench.service
92
+ end
93
+
94
+ # Current API version (sometimes we have to check it outside the GEM).
95
+ @@api = ENV['EC2_API_VERSION'] || API_VERSION
96
+ def self.api
97
+ @@api
98
+ end
99
+
100
+ # Create a new handle to an EC2 account. All handles share the same per process or per thread
101
+ # HTTP connection to Amazon EC2. Each handle is for a specific account. The params have the
102
+ # following options:
103
+ # * <tt>:endpoint_url</tt> a fully qualified url to Amazon API endpoint (this overwrites: :server, :port, :service, :protocol and :region). Example: 'https://eu-west-1.ec2.amazonaws.com/'
104
+ # * <tt>:server</tt>: EC2 service host, default: DEFAULT_HOST
105
+ # * <tt>:region</tt>: EC2 region (North America by default)
106
+ # * <tt>:port</tt>: EC2 service port, default: DEFAULT_PORT
107
+ # * <tt>:protocol</tt>: 'http' or 'https', default: DEFAULT_PROTOCOL
108
+ # * <tt>:multi_thread</tt>: true=HTTP connection per thread, false=per process
109
+ # * <tt>:logger</tt>: for log messages, default: RAILS_DEFAULT_LOGGER else STDOUT
110
+ # * <tt>:signature_version</tt>: The signature version : '0','1' or '2'(default)
111
+ # * <tt>:cache</tt>: true/false: caching for: ec2_describe_images, describe_instances,
112
+ # describe_images_by_owner, describe_images_by_executable_by, describe_availability_zones,
113
+ # describe_security_groups, describe_key_pairs, describe_addresses,
114
+ # describe_volumes, describe_snapshots methods, default: false.
115
+ #
116
+ def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
117
+ init({ :name => 'EC2',
118
+ :default_host => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).host : DEFAULT_HOST,
119
+ :default_port => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).port : DEFAULT_PORT,
120
+ :default_service => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).path : DEFAULT_PATH,
121
+ :default_protocol => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).scheme : DEFAULT_PROTOCOL,
122
+ :default_api_version => @@api },
123
+ aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'] ,
124
+ aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'],
125
+ params)
126
+ end
127
+
128
+ def generate_request(action, params={}) #:nodoc:
129
+ generate_request_impl(:get, action, params )
130
+ end
131
+
132
+ # Sends request to Amazon and parses the response
133
+ # Raises AwsError if any banana happened
134
+ def request_info(request, parser) #:nodoc:
135
+ request_info_impl(:ec2_connection, @@bench, request, parser)
136
+ end
137
+
138
+ #-----------------------------------------------------------------
139
+ # Images
140
+ #-----------------------------------------------------------------
141
+
142
+ # params:
143
+ # { 'ImageId' => ['id1', ..., 'idN'],
144
+ # 'Owner' => ['self', ..., 'userN'],
145
+ # 'ExecutableBy' => ['self', 'all', ..., 'userN']
146
+ # }
147
+ def ec2_describe_images(params={}, image_type=nil, cache_for=nil) #:nodoc:
148
+ request_hash = {}
149
+ params.each do |list_by, list|
150
+ request_hash.merge! amazonize_list(list_by, list.to_a)
151
+ end
152
+ request_hash['ImageType'] = image_type if image_type
153
+ link = generate_request("DescribeImages", request_hash)
154
+ request_cache_or_info cache_for, link, QEc2DescribeImagesParser, @@bench, cache_for
155
+ rescue Exception
156
+ on_exception
157
+ end
158
+
159
+ # Retrieve a list of images. Returns array of hashes describing the images or an exception:
160
+ # +image_type+ = 'machine' || 'kernel' || 'ramdisk'
161
+ #
162
+ # ec2.describe_images #=>
163
+ # [{:aws_owner => "522821470517",
164
+ # :aws_id => "ami-e4b6538d",
165
+ # :aws_state => "available",
166
+ # :aws_location => "marcins_cool_public_images/ubuntu-6.10.manifest.xml",
167
+ # :aws_is_public => true,
168
+ # :aws_architecture => "i386",
169
+ # :aws_image_type => "machine"},
170
+ # {...},
171
+ # {...} ]
172
+ #
173
+ # If +list+ param is set, then retrieve information about the listed images only:
174
+ #
175
+ # ec2.describe_images(['ami-e4b6538d']) #=>
176
+ # [{:aws_owner => "522821470517",
177
+ # :aws_id => "ami-e4b6538d",
178
+ # :aws_state => "available",
179
+ # :aws_location => "marcins_cool_public_images/ubuntu-6.10.manifest.xml",
180
+ # :aws_is_public => true,
181
+ # :aws_architecture => "i386",
182
+ # :aws_image_type => "machine"}]
183
+ #
184
+ def describe_images(list=[], image_type=nil)
185
+ list = list.to_a
186
+ cache_for = list.empty? && !image_type ? :describe_images : nil
187
+ ec2_describe_images({ 'ImageId' => list }, image_type, cache_for)
188
+ end
189
+
190
+ #
191
+ # Example:
192
+ #
193
+ # ec2.describe_images_by_owner('522821470517')
194
+ # ec2.describe_images_by_owner('self')
195
+ #
196
+ def describe_images_by_owner(list=['self'], image_type=nil)
197
+ list = list.to_a
198
+ cache_for = list==['self'] && !image_type ? :describe_images_by_owner : nil
199
+ ec2_describe_images({ 'Owner' => list }, image_type, cache_for)
200
+ end
201
+
202
+ #
203
+ # Example:
204
+ #
205
+ # ec2.describe_images_by_executable_by('522821470517')
206
+ # ec2.describe_images_by_executable_by('self')
207
+ # ec2.describe_images_by_executable_by('all')
208
+ #
209
+ def describe_images_by_executable_by(list=['self'], image_type=nil)
210
+ list = list.to_a
211
+ cache_for = list==['self'] && !image_type ? :describe_images_by_executable_by : nil
212
+ ec2_describe_images({ 'ExecutableBy' => list }, image_type, cache_for)
213
+ end
214
+
215
+
216
+ # Register new image at Amazon.
217
+ # Returns new image id or an exception.
218
+ #
219
+ # ec2.register_image('bucket/key/manifest') #=> 'ami-e444444d'
220
+ #
221
+ def register_image(image_location)
222
+ link = generate_request("RegisterImage",
223
+ 'ImageLocation' => image_location.to_s)
224
+ request_info(link, QEc2RegisterImageParser.new(:logger => @logger))
225
+ rescue Exception
226
+ on_exception
227
+ end
228
+
229
+ # Deregister image at Amazon. Returns +true+ or an exception.
230
+ #
231
+ # ec2.deregister_image('ami-e444444d') #=> true
232
+ #
233
+ def deregister_image(image_id)
234
+ link = generate_request("DeregisterImage",
235
+ 'ImageId' => image_id.to_s)
236
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
237
+ rescue Exception
238
+ on_exception
239
+ end
240
+
241
+
242
+ # Describe image attributes. Currently 'launchPermission', 'productCodes', 'kernel', 'ramdisk' and 'blockDeviceMapping' are supported.
243
+ #
244
+ # ec2.describe_image_attribute('ami-e444444d') #=> {:groups=>["all"], :users=>["000000000777"]}
245
+ #
246
+ def describe_image_attribute(image_id, attribute='launchPermission')
247
+ link = generate_request("DescribeImageAttribute",
248
+ 'ImageId' => image_id,
249
+ 'Attribute' => attribute)
250
+ request_info(link, QEc2DescribeImageAttributeParser.new(:logger => @logger))
251
+ rescue Exception
252
+ on_exception
253
+ end
254
+
255
+ # Reset image attribute. Currently, only 'launchPermission' is supported. Returns +true+ or an exception.
256
+ #
257
+ # ec2.reset_image_attribute('ami-e444444d') #=> true
258
+ #
259
+ def reset_image_attribute(image_id, attribute='launchPermission')
260
+ link = generate_request("ResetImageAttribute",
261
+ 'ImageId' => image_id,
262
+ 'Attribute' => attribute)
263
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
264
+ rescue Exception
265
+ on_exception
266
+ end
267
+
268
+ # Modify an image's attributes. It is recommended that you use
269
+ # modify_image_launch_perm_add_users, modify_image_launch_perm_remove_users, etc.
270
+ # instead of modify_image_attribute because the signature of
271
+ # modify_image_attribute may change with EC2 service changes.
272
+ #
273
+ # attribute : currently, only 'launchPermission' is supported.
274
+ # operation_type : currently, only 'add' & 'remove' are supported.
275
+ # vars:
276
+ # :user_group : currently, only 'all' is supported.
277
+ # :user_id
278
+ # :product_code
279
+ def modify_image_attribute(image_id, attribute, operation_type = nil, vars = {})
280
+ params = {'ImageId' => image_id,
281
+ 'Attribute' => attribute}
282
+ params['OperationType'] = operation_type if operation_type
283
+ params.update(amazonize_list('UserId', vars[:user_id].to_a)) if vars[:user_id]
284
+ params.update(amazonize_list('UserGroup', vars[:user_group].to_a)) if vars[:user_group]
285
+ params.update(amazonize_list('ProductCode', vars[:product_code])) if vars[:product_code]
286
+ link = generate_request("ModifyImageAttribute", params)
287
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
288
+ rescue Exception
289
+ on_exception
290
+ end
291
+
292
+ # Grant image launch permissions to users.
293
+ # Parameter +userId+ is a list of user AWS account ids.
294
+ # Returns +true+ or an exception.
295
+ #
296
+ # ec2.modify_image_launch_perm_add_users('ami-e444444d',['000000000777','000000000778']) #=> true
297
+ def modify_image_launch_perm_add_users(image_id, user_id=[])
298
+ modify_image_attribute(image_id, 'launchPermission', 'add', :user_id => user_id.to_a)
299
+ end
300
+
301
+ # Revokes image launch permissions for users. +userId+ is a list of users AWS accounts ids. Returns +true+ or an exception.
302
+ #
303
+ # ec2.modify_image_launch_perm_remove_users('ami-e444444d',['000000000777','000000000778']) #=> true
304
+ #
305
+ def modify_image_launch_perm_remove_users(image_id, user_id=[])
306
+ modify_image_attribute(image_id, 'launchPermission', 'remove', :user_id => user_id.to_a)
307
+ end
308
+
309
+ # Add image launch permissions for users groups (currently only 'all' is supported, which gives public launch permissions).
310
+ # Returns +true+ or an exception.
311
+ #
312
+ # ec2.modify_image_launch_perm_add_groups('ami-e444444d') #=> true
313
+ #
314
+ def modify_image_launch_perm_add_groups(image_id, user_group=['all'])
315
+ modify_image_attribute(image_id, 'launchPermission', 'add', :user_group => user_group.to_a)
316
+ end
317
+
318
+ # Remove image launch permissions for users groups (currently only 'all' is supported, which gives public launch permissions).
319
+ #
320
+ # ec2.modify_image_launch_perm_remove_groups('ami-e444444d') #=> true
321
+ #
322
+ def modify_image_launch_perm_remove_groups(image_id, user_group=['all'])
323
+ modify_image_attribute(image_id, 'launchPermission', 'remove', :user_group => user_group.to_a)
324
+ end
325
+
326
+ # Add product code to image
327
+ #
328
+ # ec2.modify_image_product_code('ami-e444444d','0ABCDEF') #=> true
329
+ #
330
+ def modify_image_product_code(image_id, product_code=[])
331
+ modify_image_attribute(image_id, 'productCodes', nil, :product_code => product_code.to_a)
332
+ end
333
+
334
+ #-----------------------------------------------------------------
335
+ # Instances
336
+ #-----------------------------------------------------------------
337
+
338
+ def get_desc_instances(instances) # :nodoc:
339
+ result = []
340
+ instances.each do |reservation|
341
+ reservation[:instances_set].each do |instance|
342
+ # Parse and remove timestamp from the reason string. The timestamp is of
343
+ # the request, not when EC2 took action, thus confusing & useless...
344
+ instance[:aws_reason] = instance[:aws_reason].sub(/\(\d[^)]*GMT\) */, '')
345
+ instance[:aws_owner] = reservation[:aws_owner]
346
+ instance[:aws_reservation_id] = reservation[:aws_reservation_id]
347
+ instance[:aws_groups] = reservation[:aws_groups]
348
+ result << instance
349
+ end
350
+ end
351
+ result
352
+ rescue Exception
353
+ on_exception
354
+ end
355
+
356
+ # Retrieve information about EC2 instances. If +list+ is omitted then returns the
357
+ # list of all instances.
358
+ #
359
+ # ec2.describe_instances #=>
360
+ # [{:aws_image_id => "ami-e444444d",
361
+ # :aws_reason => "",
362
+ # :aws_state_code => "16",
363
+ # :aws_owner => "000000000888",
364
+ # :aws_instance_id => "i-123f1234",
365
+ # :aws_reservation_id => "r-aabbccdd",
366
+ # :aws_state => "running",
367
+ # :dns_name => "domU-12-34-67-89-01-C9.usma2.compute.amazonaws.com",
368
+ # :ssh_key_name => "staging",
369
+ # :aws_groups => ["default"],
370
+ # :private_dns_name => "domU-12-34-67-89-01-C9.usma2.compute.amazonaws.com",
371
+ # :aws_instance_type => "m1.small",
372
+ # :aws_launch_time => "2008-1-1T00:00:00.000Z"},
373
+ # :aws_availability_zone => "us-east-1b",
374
+ # :aws_kernel_id => "aki-ba3adfd3",
375
+ # :aws_ramdisk_id => "ari-badbad00",
376
+ # ..., {...}]
377
+ #
378
+ def describe_instances(list=[])
379
+ link = generate_request("DescribeInstances", amazonize_list('InstanceId',list.to_a))
380
+ request_cache_or_info(:describe_instances, link, QEc2DescribeInstancesParser, @@bench, list.blank?) do |parser|
381
+ get_desc_instances(parser.result)
382
+ end
383
+ rescue Exception
384
+ on_exception
385
+ end
386
+
387
+ # Return the product code attached to instance or +nil+ otherwise.
388
+ #
389
+ # ec2.confirm_product_instance('ami-e444444d','12345678') #=> nil
390
+ # ec2.confirm_product_instance('ami-e444444d','00001111') #=> "000000000888"
391
+ #
392
+ def confirm_product_instance(instance, product_code)
393
+ link = generate_request("ConfirmProductInstance", { 'ProductCode' => product_code,
394
+ 'InstanceId' => instance })
395
+ request_info(link, QEc2ConfirmProductInstanceParser.new(:logger => @logger))
396
+ end
397
+
398
+ # Launch new EC2 instances. Returns a list of launched instances or an exception.
399
+ #
400
+ # ec2.run_instances('ami-e444444d',1,1,['my_awesome_group'],'my_awesome_key', 'Woohoo!!!', 'public') #=>
401
+ # [{:aws_image_id => "ami-e444444d",
402
+ # :aws_reason => "",
403
+ # :aws_state_code => "0",
404
+ # :aws_owner => "000000000888",
405
+ # :aws_instance_id => "i-123f1234",
406
+ # :aws_reservation_id => "r-aabbccdd",
407
+ # :aws_state => "pending",
408
+ # :dns_name => "",
409
+ # :ssh_key_name => "my_awesome_key",
410
+ # :aws_groups => ["my_awesome_group"],
411
+ # :private_dns_name => "",
412
+ # :aws_instance_type => "m1.small",
413
+ # :aws_launch_time => "2008-1-1T00:00:00.000Z"
414
+ # :aws_ramdisk_id => "ari-8605e0ef"
415
+ # :aws_kernel_id => "aki-9905e0f0",
416
+ # :ami_launch_index => "0",
417
+ # :aws_availability_zone => "us-east-1b"
418
+ # }]
419
+ #
420
+ def run_instances(image_id, min_count, max_count, group_ids, key_name, user_data='',
421
+ addressing_type = nil, instance_type = nil,
422
+ kernel_id = nil, ramdisk_id = nil, availability_zone = nil,
423
+ block_device_mappings = nil)
424
+ launch_instances(image_id, { :min_count => min_count,
425
+ :max_count => max_count,
426
+ :user_data => user_data,
427
+ :group_ids => group_ids,
428
+ :key_name => key_name,
429
+ :instance_type => instance_type,
430
+ :addressing_type => addressing_type,
431
+ :kernel_id => kernel_id,
432
+ :ramdisk_id => ramdisk_id,
433
+ :availability_zone => availability_zone,
434
+ :block_device_mappings => block_device_mappings
435
+ })
436
+ end
437
+
438
+
439
+ # Launch new EC2 instances. Returns a list of launched instances or an exception.
440
+ #
441
+ # +lparams+ keys (default values in parenthesis):
442
+ # :min_count fixnum, (1)
443
+ # :max_count fixnum, (1)
444
+ # :group_ids array or string ([] == 'default')
445
+ # :instance_type string (DEFAULT_INSTACE_TYPE)
446
+ # :addressing_type string (DEFAULT_ADDRESSING_TYPE
447
+ # :key_name string
448
+ # :kernel_id string
449
+ # :ramdisk_id string
450
+ # :availability_zone string
451
+ # :block_device_mappings string
452
+ # :user_data string
453
+ #
454
+ # ec2.launch_instances('ami-e444444d', :group_ids => 'my_awesome_group',
455
+ # :user_data => "Woohoo!!!",
456
+ # :addressing_type => "public",
457
+ # :key_name => "my_awesome_key",
458
+ # :availability_zone => "us-east-1c") #=>
459
+ # [{:aws_image_id => "ami-e444444d",
460
+ # :aws_reason => "",
461
+ # :aws_state_code => "0",
462
+ # :aws_owner => "000000000888",
463
+ # :aws_instance_id => "i-123f1234",
464
+ # :aws_reservation_id => "r-aabbccdd",
465
+ # :aws_state => "pending",
466
+ # :dns_name => "",
467
+ # :ssh_key_name => "my_awesome_key",
468
+ # :aws_groups => ["my_awesome_group"],
469
+ # :private_dns_name => "",
470
+ # :aws_instance_type => "m1.small",
471
+ # :aws_launch_time => "2008-1-1T00:00:00.000Z",
472
+ # :aws_ramdisk_id => "ari-8605e0ef"
473
+ # :aws_kernel_id => "aki-9905e0f0",
474
+ # :ami_launch_index => "0",
475
+ # :aws_availability_zone => "us-east-1c"
476
+ # }]
477
+ #
478
+ def launch_instances(image_id, lparams={})
479
+ @logger.info("Launching instance of image #{image_id} for #{@aws_access_key_id}, " +
480
+ "key: #{lparams[:key_name]}, groups: #{(lparams[:group_ids]).to_a.join(',')}")
481
+ # careful: keyName and securityGroups may be nil
482
+ params = amazonize_list('SecurityGroup', lparams[:group_ids].to_a)
483
+ params.update( {'ImageId' => image_id,
484
+ 'MinCount' => (lparams[:min_count] || 1).to_s,
485
+ 'MaxCount' => (lparams[:max_count] || 1).to_s,
486
+ 'AddressingType' => lparams[:addressing_type] || DEFAULT_ADDRESSING_TYPE,
487
+ 'InstanceType' => lparams[:instance_type] || DEFAULT_INSTANCE_TYPE })
488
+ # optional params
489
+ params['KeyName'] = lparams[:key_name] unless lparams[:key_name].blank?
490
+ params['KernelId'] = lparams[:kernel_id] unless lparams[:kernel_id].blank?
491
+ params['RamdiskId'] = lparams[:ramdisk_id] unless lparams[:ramdisk_id].blank?
492
+ params['Placement.AvailabilityZone'] = lparams[:availability_zone] unless lparams[:availability_zone].blank?
493
+ params['BlockDeviceMappings'] = lparams[:block_device_mappings] unless lparams[:block_device_mappings].blank?
494
+ unless lparams[:user_data].blank?
495
+ lparams[:user_data].strip!
496
+ # Do not use CGI::escape(encode64(...)) as it is done in Amazons EC2 library.
497
+ # Amazon 169.254.169.254 does not like escaped symbols!
498
+ # And it doesn't like "\n" inside of encoded string! Grrr....
499
+ # Otherwise, some of UserData symbols will be lost...
500
+ params['UserData'] = Base64.encode64(lparams[:user_data]).delete("\n") unless lparams[:user_data].blank?
501
+ end
502
+ link = generate_request("RunInstances", params)
503
+ #debugger
504
+ instances = request_info(link, QEc2DescribeInstancesParser.new(:logger => @logger))
505
+ get_desc_instances(instances)
506
+ rescue Exception
507
+ on_exception
508
+ end
509
+
510
+ # Terminates EC2 instances. Returns a list of termination params or an exception.
511
+ #
512
+ # ec2.terminate_instances(['i-f222222d','i-f222222e']) #=>
513
+ # [{:aws_shutdown_state => "shutting-down",
514
+ # :aws_instance_id => "i-f222222d",
515
+ # :aws_shutdown_state_code => 32,
516
+ # :aws_prev_state => "running",
517
+ # :aws_prev_state_code => 16},
518
+ # {:aws_shutdown_state => "shutting-down",
519
+ # :aws_instance_id => "i-f222222e",
520
+ # :aws_shutdown_state_code => 32,
521
+ # :aws_prev_state => "running",
522
+ # :aws_prev_state_code => 16}]
523
+ #
524
+ def terminate_instances(list=[])
525
+ link = generate_request("TerminateInstances", amazonize_list('InstanceId',list.to_a))
526
+ request_info(link, QEc2TerminateInstancesParser.new(:logger => @logger))
527
+ rescue Exception
528
+ on_exception
529
+ end
530
+
531
+ # Retreive EC2 instance OS logs. Returns a hash of data or an exception.
532
+ #
533
+ # ec2.get_console_output('i-f222222d') =>
534
+ # {:aws_instance_id => 'i-f222222d',
535
+ # :aws_timestamp => "2007-05-23T14:36:07.000-07:00",
536
+ # :timestamp => Wed May 23 21:36:07 UTC 2007, # Time instance
537
+ # :aws_output => "Linux version 2.6.16-xenU (builder@patchbat.amazonsa) (gcc version 4.0.1 20050727 ..."
538
+ def get_console_output(instance_id)
539
+ link = generate_request("GetConsoleOutput", { 'InstanceId.1' => instance_id })
540
+ request_info(link, QEc2GetConsoleOutputParser.new(:logger => @logger))
541
+ rescue Exception
542
+ on_exception
543
+ end
544
+
545
+ # Reboot an EC2 instance. Returns +true+ or an exception.
546
+ #
547
+ # ec2.reboot_instances(['i-f222222d','i-f222222e']) #=> true
548
+ #
549
+ def reboot_instances(list)
550
+ link = generate_request("RebootInstances", amazonize_list('InstanceId', list.to_a))
551
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
552
+ rescue Exception
553
+ on_exception
554
+ end
555
+
556
+ #-----------------------------------------------------------------
557
+ # Instances: Windows addons
558
+ #-----------------------------------------------------------------
559
+
560
+ # Get initial Windows Server setup password from an instance console output.
561
+ #
562
+ # my_awesome_key = ec2.create_key_pair('my_awesome_key') #=>
563
+ # {:aws_key_name => "my_awesome_key",
564
+ # :aws_fingerprint => "01:02:03:f4:25:e6:97:e8:9b:02:1a:26:32:4e:58:6b:7a:8c:9f:03",
565
+ # :aws_material => "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAK...Q8MDrCbuQ=\n-----END RSA PRIVATE KEY-----"}
566
+ #
567
+ # my_awesome_instance = ec2.run_instances('ami-a000000a',1,1,['my_awesome_group'],'my_awesome_key', 'WindowsInstance!!!') #=>
568
+ # [{:aws_image_id => "ami-a000000a",
569
+ # :aws_instance_id => "i-12345678",
570
+ # ...
571
+ # :aws_availability_zone => "us-east-1b"
572
+ # }]
573
+ #
574
+ # # wait until instance enters 'operational' state and get it's initial password
575
+ #
576
+ # puts ec2.get_initial_password(my_awesome_instance[:aws_instance_id], my_awesome_key[:aws_material]) #=> "MhjWcgZuY6"
577
+ #
578
+ def get_initial_password(instance_id, private_key)
579
+ console_output = get_console_output(instance_id)
580
+ crypted_password = console_output[:aws_output][%r{<Password>(.+)</Password>}m] && $1
581
+ unless crypted_password
582
+ raise AwsError.new("Initial password was not found in console output for #{instance_id}")
583
+ else
584
+ OpenSSL::PKey::RSA.new(private_key).private_decrypt(Base64.decode64(crypted_password))
585
+ end
586
+ rescue Exception
587
+ on_exception
588
+ end
589
+
590
+ # Bundle a Windows image.
591
+ # Internally, it queues the bundling task and shuts down the instance.
592
+ # It then takes a snapshot of the Windows volume bundles it, and uploads it to
593
+ # S3. After bundling completes, Rightaws::Ec2#register_image may be used to
594
+ # register the new Windows AMI for subsequent launches.
595
+ #
596
+ # ec2.bundle_instance('i-e3e24e8a', 'my-awesome-bucket', 'my-win-image-1') #=>
597
+ # [{:aws_update_time => "2008-10-16T13:58:25.000Z",
598
+ # :s3_bucket => "kd-win-1",
599
+ # :s3_prefix => "win2pr",
600
+ # :aws_state => "pending",
601
+ # :aws_id => "bun-26a7424f",
602
+ # :aws_instance_id => "i-878a25ee",
603
+ # :aws_start_time => "2008-10-16T13:58:02.000Z"}]
604
+ #
605
+ def bundle_instance(instance_id, s3_bucket, s3_prefix,
606
+ s3_owner_aws_access_key_id=nil, s3_owner_aws_secret_access_key=nil,
607
+ s3_expires = S3Interface::DEFAULT_EXPIRES_AFTER,
608
+ s3_upload_policy='ec2-bundle-read')
609
+ # S3 access and signatures
610
+ s3_owner_aws_access_key_id ||= @aws_access_key_id
611
+ s3_owner_aws_secret_access_key ||= @aws_secret_access_key
612
+ s3_expires = Time.now.utc + s3_expires if s3_expires.is_a?(Fixnum) && (s3_expires < S3Interface::ONE_YEAR_IN_SECONDS)
613
+ # policy
614
+ policy = { 'expiration' => s3_expires.strftime('%Y-%m-%dT%H:%M:%SZ'),
615
+ 'conditions' => [ { 'bucket' => s3_bucket },
616
+ { 'acl' => s3_upload_policy },
617
+ [ 'starts-with', '$key', s3_prefix ] ] }.to_json
618
+ policy64 = Base64.encode64(policy).gsub("\n","")
619
+ signed_policy64 = AwsUtils.sign(s3_owner_aws_secret_access_key, policy64)
620
+ # fill request params
621
+ params = { 'InstanceId' => instance_id,
622
+ 'Storage.S3.AWSAccessKeyId' => s3_owner_aws_access_key_id,
623
+ 'Storage.S3.UploadPolicy' => policy64,
624
+ 'Storage.S3.UploadPolicySignature' => signed_policy64,
625
+ 'Storage.S3.Bucket' => s3_bucket,
626
+ 'Storage.S3.Prefix' => s3_prefix,
627
+ }
628
+ link = generate_request("BundleInstance", params)
629
+ request_info(link, QEc2BundleInstanceParser.new)
630
+ rescue Exception
631
+ on_exception
632
+ end
633
+
634
+ # Describe the status of the Windows AMI bundlings.
635
+ # If +list+ is omitted the returns the whole list of tasks.
636
+ #
637
+ # ec2.describe_bundle_tasks(['bun-4fa74226']) #=>
638
+ # [{:s3_bucket => "my-awesome-bucket"
639
+ # :aws_id => "bun-0fa70206",
640
+ # :s3_prefix => "win1pr",
641
+ # :aws_start_time => "2008-10-14T16:27:57.000Z",
642
+ # :aws_update_time => "2008-10-14T16:37:10.000Z",
643
+ # :aws_error_code => "Client.S3Error",
644
+ # :aws_error_message =>
645
+ # "AccessDenied(403)- Invalid according to Policy: Policy Condition failed: [\"eq\", \"$acl\", \"aws-exec-read\"]",
646
+ # :aws_state => "failed",
647
+ # :aws_instance_id => "i-e3e24e8a"}]
648
+ #
649
+ def describe_bundle_tasks(list=[])
650
+ link = generate_request("DescribeBundleTasks", amazonize_list('BundleId', list.to_a))
651
+ request_info(link, QEc2DescribeBundleTasksParser.new)
652
+ rescue Exception
653
+ on_exception
654
+ end
655
+
656
+ # Cancel an in‐progress or pending bundle task by id.
657
+ #
658
+ # ec2.cancel_bundle_task('bun-73a7421a') #=>
659
+ # [{:s3_bucket => "my-awesome-bucket"
660
+ # :aws_id => "bun-0fa70206",
661
+ # :s3_prefix => "win02",
662
+ # :aws_start_time => "2008-10-14T13:00:29.000Z",
663
+ # :aws_error_message => "User has requested bundling operation cancellation",
664
+ # :aws_state => "failed",
665
+ # :aws_update_time => "2008-10-14T13:01:31.000Z",
666
+ # :aws_error_code => "Client.Cancelled",
667
+ # :aws_instance_id => "i-e3e24e8a"}
668
+ #
669
+ def cancel_bundle_task(bundle_id)
670
+ link = generate_request("CancelBundleTask", { 'BundleId' => bundle_id })
671
+ request_info(link, QEc2BundleInstanceParser.new)
672
+ rescue Exception
673
+ on_exception
674
+ end
675
+
676
+ #-----------------------------------------------------------------
677
+ # Reserved instances
678
+ #-----------------------------------------------------------------
679
+
680
+ # Retrieve reserved instances list.
681
+ # Returns a list of Reserved Instances.
682
+ #
683
+ # ec2.describe_reserved_instances #=>
684
+ # [{:aws_id=>"1ba8e2e3-1c40-434c-a741-5ff16a4c542e",
685
+ # :aws_duration=>31536000,
686
+ # :aws_instance_type=>"m1.small",
687
+ # :aws_usage_price=>0.03,
688
+ # :aws_availability_zone=>"us-east-1b",
689
+ # :aws_state=>"payment-pending",
690
+ # :aws_product_description=>"Test",
691
+ # :aws_fixed_price=>325.0,
692
+ # :aws_instance_count=>1}]
693
+ #
694
+ def describe_reserved_instances(list=[])
695
+ link = generate_request("DescribeReservedInstances", amazonize_list('ReservedInstancesId',list.to_a))
696
+ request_cache_or_info(:describe_reserved_instances, link, QEc2DescribeReservedInstancesParser, @@bench, list.blank?)
697
+ rescue Exception
698
+ on_exception
699
+ end
700
+
701
+ # Retrieve reserved instances offerings.
702
+ # Returns a set of available offerings.
703
+ #
704
+ # Optional params:
705
+ # :aws_ids => String || Array
706
+ # :aws_instance_type => String
707
+ # :aws_availability_zone => String
708
+ # :aws_product_description => String
709
+ #
710
+ # ec2.describe_reserved_instances_offerings #=>
711
+ # [{:aws_instance_type=>"c1.medium",
712
+ # :aws_availability_zone=>"us-east-1c",
713
+ # :aws_duration=>94608000,
714
+ # :aws_product_description=>"Linux/UNIX",
715
+ # :aws_id=>"e5a2ff3b-f6eb-4b4e-83f8-b879d7060257",
716
+ # :aws_usage_price=>0.06,
717
+ # :aws_fixed_price=>1000.0},
718
+ # ...
719
+ # {:aws_instance_type=>"m1.xlarge",
720
+ # :aws_availability_zone=>"us-east-1a",
721
+ # :aws_duration=>31536000,
722
+ # :aws_product_description=>"Linux/UNIX",
723
+ # :aws_id=>"c48ab04c-63ab-4cd6-b8f5-978a29eb9bcc",
724
+ # :aws_usage_price=>0.24,
725
+ # :aws_fixed_price=>2600.0}]
726
+ #
727
+ def describe_reserved_instances_offerings(params={})
728
+ rparams = {}
729
+ rparams.update(amazonize_list('ReservedInstancesOfferingId', params[:aws_ids].to_a)) if params[:aws_ids]
730
+ rparams['InstanceType'] = params[:aws_instance_type] if params[:aws_instance_type]
731
+ rparams['AvailabilityZone'] = params[:aws_availability_zone] if params[:aws_availability_zone]
732
+ rparams['ProductDescription'] = params[:aws_product_description] if params[:aws_product_description]
733
+ link = generate_request("DescribeReservedInstancesOfferings", rparams)
734
+ request_cache_or_info(:describe_reserved_instances_offerings, link, QEc2DescribeReservedInstancesOfferingsParser, @@bench, params.blank?)
735
+ rescue Exception
736
+ on_exception
737
+ end
738
+
739
+ # Purchase a Reserved Instance.
740
+ # Returns ReservedInstancesId value.
741
+ #
742
+ # ec2.purchase_reserved_instances_offering('e5a2ff3b-f6eb-4b4e-83f8-b879d7060257', 3) # => '4b2293b4-5813-4cc8-9ce3-1957fc1dcfc8'
743
+ #
744
+ def purchase_reserved_instances_offering(reserved_instances_offering_id, instance_count=1)
745
+ link = generate_request("PurchaseReservedInstancesOffering", { 'ReservedInstancesOfferingId' => reserved_instances_offering_id,
746
+ 'InstanceCount' => instance_count })
747
+ request_info(link, QEc2PurchaseReservedInstancesOfferingParser.new)
748
+ rescue Exception
749
+ on_exception
750
+ end
751
+
752
+ #-----------------------------------------------------------------
753
+ # Security groups
754
+ #-----------------------------------------------------------------
755
+
756
+ # Retrieve Security Group information. If +list+ is omitted the returns the whole list of groups.
757
+ #
758
+ # ec2.describe_security_groups #=>
759
+ # [{:aws_group_name => "default-1",
760
+ # :aws_owner => "000000000888",
761
+ # :aws_description => "Default allowing SSH, HTTP, and HTTPS ingress",
762
+ # :aws_perms =>
763
+ # [{:owner => "000000000888", :group => "default"},
764
+ # {:owner => "000000000888", :group => "default-1"},
765
+ # {:to_port => "-1", :protocol => "icmp", :from_port => "-1", :cidr_ips => "0.0.0.0/0"},
766
+ # {:to_port => "22", :protocol => "tcp", :from_port => "22", :cidr_ips => "0.0.0.0/0"},
767
+ # {:to_port => "80", :protocol => "tcp", :from_port => "80", :cidr_ips => "0.0.0.0/0"},
768
+ # {:to_port => "443", :protocol => "tcp", :from_port => "443", :cidr_ips => "0.0.0.0/0"}]},
769
+ # ..., {...}]
770
+ #
771
+ def describe_security_groups(list=[])
772
+ link = generate_request("DescribeSecurityGroups", amazonize_list('GroupName',list.to_a))
773
+ request_cache_or_info( :describe_security_groups, link, QEc2DescribeSecurityGroupsParser, @@bench, list.blank?) do |parser|
774
+ result = []
775
+ parser.result.each do |item|
776
+ perms = []
777
+ item.ipPermissions.each do |perm|
778
+ perm.groups.each do |ngroup|
779
+ perms << {:group => ngroup.groupName,
780
+ :owner => ngroup.userId}
781
+ end
782
+ perm.ipRanges.each do |cidr_ip|
783
+ perms << {:from_port => perm.fromPort,
784
+ :to_port => perm.toPort,
785
+ :protocol => perm.ipProtocol,
786
+ :cidr_ips => cidr_ip}
787
+ end
788
+ end
789
+
790
+ # delete duplication
791
+ perms.each_index do |i|
792
+ (0...i).each do |j|
793
+ if perms[i] == perms[j] then perms[i] = nil; break; end
794
+ end
795
+ end
796
+ perms.compact!
797
+
798
+ result << {:aws_owner => item.ownerId,
799
+ :aws_group_name => item.groupName,
800
+ :aws_description => item.groupDescription,
801
+ :aws_perms => perms}
802
+
803
+ end
804
+ result
805
+ end
806
+ rescue Exception
807
+ on_exception
808
+ end
809
+
810
+ # Create new Security Group. Returns +true+ or an exception.
811
+ #
812
+ # ec2.create_security_group('default-1',"Default allowing SSH, HTTP, and HTTPS ingress") #=> true
813
+ #
814
+ def create_security_group(name, description)
815
+ # EC2 doesn't like an empty description...
816
+ description = " " if description.blank?
817
+ link = generate_request("CreateSecurityGroup",
818
+ 'GroupName' => name.to_s,
819
+ 'GroupDescription' => description.to_s)
820
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
821
+ rescue Exception
822
+ on_exception
823
+ end
824
+
825
+ # Remove Security Group. Returns +true+ or an exception.
826
+ #
827
+ # ec2.delete_security_group('default-1') #=> true
828
+ #
829
+ def delete_security_group(name)
830
+ link = generate_request("DeleteSecurityGroup",
831
+ 'GroupName' => name.to_s)
832
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
833
+ rescue Exception
834
+ on_exception
835
+ end
836
+
837
+ # Authorize named ingress for security group. Allows instances that are member of someone
838
+ # else's security group to open connections to instances in my group.
839
+ #
840
+ # ec2.authorize_security_group_named_ingress('my_awesome_group', '7011-0219-8268', 'their_group_name') #=> true
841
+ #
842
+ def authorize_security_group_named_ingress(name, owner, group)
843
+ link = generate_request("AuthorizeSecurityGroupIngress",
844
+ 'GroupName' => name.to_s,
845
+ 'SourceSecurityGroupName' => group.to_s,
846
+ 'SourceSecurityGroupOwnerId' => owner.to_s.gsub(/-/,''))
847
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
848
+ rescue Exception
849
+ on_exception
850
+ end
851
+
852
+ # Revoke named ingress for security group.
853
+ #
854
+ # ec2.revoke_security_group_named_ingress('my_awesome_group', aws_user_id, 'another_group_name') #=> true
855
+ #
856
+ def revoke_security_group_named_ingress(name, owner, group)
857
+ link = generate_request("RevokeSecurityGroupIngress",
858
+ 'GroupName' => name.to_s,
859
+ 'SourceSecurityGroupName' => group.to_s,
860
+ 'SourceSecurityGroupOwnerId' => owner.to_s.gsub(/-/,''))
861
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
862
+ rescue Exception
863
+ on_exception
864
+ end
865
+
866
+ # Add permission to a security group. Returns +true+ or an exception. +protocol+ is one of :'tcp'|'udp'|'icmp'.
867
+ #
868
+ # ec2.authorize_security_group_IP_ingress('my_awesome_group', 80, 82, 'udp', '192.168.1.0/8') #=> true
869
+ # ec2.authorize_security_group_IP_ingress('my_awesome_group', -1, -1, 'icmp') #=> true
870
+ #
871
+ def authorize_security_group_IP_ingress(name, from_port, to_port, protocol='tcp', cidr_ip='0.0.0.0/0')
872
+ link = generate_request("AuthorizeSecurityGroupIngress",
873
+ 'GroupName' => name.to_s,
874
+ 'IpProtocol' => protocol.to_s,
875
+ 'FromPort' => from_port.to_s,
876
+ 'ToPort' => to_port.to_s,
877
+ 'CidrIp' => cidr_ip.to_s)
878
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
879
+ rescue Exception
880
+ on_exception
881
+ end
882
+
883
+ # Remove permission from a security group. Returns +true+ or an exception. +protocol+ is one of :'tcp'|'udp'|'icmp' ('tcp' is default).
884
+ #
885
+ # ec2.revoke_security_group_IP_ingress('my_awesome_group', 80, 82, 'udp', '192.168.1.0/8') #=> true
886
+ #
887
+ def revoke_security_group_IP_ingress(name, from_port, to_port, protocol='tcp', cidr_ip='0.0.0.0/0')
888
+ link = generate_request("RevokeSecurityGroupIngress",
889
+ 'GroupName' => name.to_s,
890
+ 'IpProtocol' => protocol.to_s,
891
+ 'FromPort' => from_port.to_s,
892
+ 'ToPort' => to_port.to_s,
893
+ 'CidrIp' => cidr_ip.to_s)
894
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
895
+ rescue Exception
896
+ on_exception
897
+ end
898
+
899
+ #-----------------------------------------------------------------
900
+ # Keys
901
+ #-----------------------------------------------------------------
902
+
903
+ # Retrieve a list of SSH keys. Returns an array of keys or an exception. Each key is
904
+ # represented as a two-element hash.
905
+ #
906
+ # ec2.describe_key_pairs #=>
907
+ # [{:aws_fingerprint=> "01:02:03:f4:25:e6:97:e8:9b:02:1a:26:32:4e:58:6b:7a:8c:9f:03", :aws_key_name=>"key-1"},
908
+ # {:aws_fingerprint=> "1e:29:30:47:58:6d:7b:8c:9f:08:11:20:3c:44:52:69:74:80:97:08", :aws_key_name=>"key-2"},
909
+ # ..., {...} ]
910
+ #
911
+ def describe_key_pairs(list=[])
912
+ link = generate_request("DescribeKeyPairs", amazonize_list('KeyName',list.to_a))
913
+ request_cache_or_info :describe_key_pairs, link, QEc2DescribeKeyPairParser, @@bench, list.blank?
914
+ rescue Exception
915
+ on_exception
916
+ end
917
+
918
+ # Create new SSH key. Returns a hash of the key's data or an exception.
919
+ #
920
+ # ec2.create_key_pair('my_awesome_key') #=>
921
+ # {:aws_key_name => "my_awesome_key",
922
+ # :aws_fingerprint => "01:02:03:f4:25:e6:97:e8:9b:02:1a:26:32:4e:58:6b:7a:8c:9f:03",
923
+ # :aws_material => "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAK...Q8MDrCbuQ=\n-----END RSA PRIVATE KEY-----"}
924
+ #
925
+ def create_key_pair(name)
926
+ link = generate_request("CreateKeyPair",
927
+ 'KeyName' => name.to_s)
928
+ request_info(link, QEc2CreateKeyPairParser.new(:logger => @logger))
929
+ rescue Exception
930
+ on_exception
931
+ end
932
+
933
+ # Delete a key pair. Returns +true+ or an exception.
934
+ #
935
+ # ec2.delete_key_pair('my_awesome_key') #=> true
936
+ #
937
+ def delete_key_pair(name)
938
+ link = generate_request("DeleteKeyPair",
939
+ 'KeyName' => name.to_s)
940
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
941
+ rescue Exception
942
+ on_exception
943
+ end
944
+
945
+ #-----------------------------------------------------------------
946
+ # Elastic IPs
947
+ #-----------------------------------------------------------------
948
+
949
+ # Acquire a new elastic IP address for use with your account.
950
+ # Returns allocated IP address or an exception.
951
+ #
952
+ # ec2.allocate_address #=> '75.101.154.140'
953
+ #
954
+ def allocate_address
955
+ link = generate_request("AllocateAddress")
956
+ request_info(link, QEc2AllocateAddressParser.new(:logger => @logger))
957
+ rescue Exception
958
+ on_exception
959
+ end
960
+
961
+ # Associate an elastic IP address with an instance.
962
+ # Returns +true+ or an exception.
963
+ #
964
+ # ec2.associate_address('i-d630cbbf', '75.101.154.140') #=> true
965
+ #
966
+ def associate_address(instance_id, public_ip)
967
+ link = generate_request("AssociateAddress",
968
+ "InstanceId" => instance_id.to_s,
969
+ "PublicIp" => public_ip.to_s)
970
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
971
+ rescue Exception
972
+ on_exception
973
+ end
974
+
975
+ # List elastic IP addresses assigned to your account.
976
+ # Returns an array of 2 keys (:instance_id and :public_ip) hashes:
977
+ #
978
+ # ec2.describe_addresses #=> [{:instance_id=>"i-d630cbbf", :public_ip=>"75.101.154.140"},
979
+ # {:instance_id=>nil, :public_ip=>"75.101.154.141"}]
980
+ #
981
+ # ec2.describe_addresses('75.101.154.140') #=> [{:instance_id=>"i-d630cbbf", :public_ip=>"75.101.154.140"}]
982
+ #
983
+ def describe_addresses(list=[])
984
+ link = generate_request("DescribeAddresses",
985
+ amazonize_list('PublicIp',list.to_a))
986
+ request_cache_or_info :describe_addresses, link, QEc2DescribeAddressesParser, @@bench, list.blank?
987
+ rescue Exception
988
+ on_exception
989
+ end
990
+
991
+ # Disassociate the specified elastic IP address from the instance to which it is assigned.
992
+ # Returns +true+ or an exception.
993
+ #
994
+ # ec2.disassociate_address('75.101.154.140') #=> true
995
+ #
996
+ def disassociate_address(public_ip)
997
+ link = generate_request("DisassociateAddress",
998
+ "PublicIp" => public_ip.to_s)
999
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
1000
+ rescue Exception
1001
+ on_exception
1002
+ end
1003
+
1004
+ # Release an elastic IP address associated with your account.
1005
+ # Returns +true+ or an exception.
1006
+ #
1007
+ # ec2.release_address('75.101.154.140') #=> true
1008
+ #
1009
+ def release_address(public_ip)
1010
+ link = generate_request("ReleaseAddress",
1011
+ "PublicIp" => public_ip.to_s)
1012
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
1013
+ rescue Exception
1014
+ on_exception
1015
+ end
1016
+
1017
+ #-----------------------------------------------------------------
1018
+ # Availability zones
1019
+ #-----------------------------------------------------------------
1020
+
1021
+ # Describes availability zones that are currently available to the account and their states.
1022
+ # Returns an array of 2 keys (:zone_name and :zone_state) hashes:
1023
+ #
1024
+ # ec2.describe_availability_zones #=> [{:region_name=>"us-east-1",
1025
+ # :zone_name=>"us-east-1a",
1026
+ # :zone_state=>"available"}, ... ]
1027
+ #
1028
+ # ec2.describe_availability_zones('us-east-1c') #=> [{:region_name=>"us-east-1",
1029
+ # :zone_state=>"available",
1030
+ # :zone_name=>"us-east-1c"}]
1031
+ #
1032
+ def describe_availability_zones(list=[])
1033
+ link = generate_request("DescribeAvailabilityZones",
1034
+ amazonize_list('ZoneName',list.to_a))
1035
+ request_cache_or_info :describe_availability_zones, link, QEc2DescribeAvailabilityZonesParser, @@bench, list.blank?
1036
+ rescue Exception
1037
+ on_exception
1038
+ end
1039
+
1040
+ #-----------------------------------------------------------------
1041
+ # Regions
1042
+ #-----------------------------------------------------------------
1043
+
1044
+ # Describe regions.
1045
+ #
1046
+ # ec2.describe_regions #=> ["eu-west-1", "us-east-1"]
1047
+ #
1048
+ def describe_regions(list=[])
1049
+ link = generate_request("DescribeRegions",
1050
+ amazonize_list('RegionName',list.to_a))
1051
+ request_cache_or_info :describe_regions, link, QEc2DescribeRegionsParser, @@bench, list.blank?
1052
+ rescue Exception
1053
+ on_exception
1054
+ end
1055
+
1056
+
1057
+ #-----------------------------------------------------------------
1058
+ # EBS: Volumes
1059
+ #-----------------------------------------------------------------
1060
+
1061
+ # Describe all EBS volumes.
1062
+ #
1063
+ # ec2.describe_volumes #=>
1064
+ # [{:aws_size => 94,
1065
+ # :aws_device => "/dev/sdc",
1066
+ # :aws_attachment_status => "attached",
1067
+ # :zone => "merlot",
1068
+ # :snapshot_id => nil,
1069
+ # :aws_attached_at => Wed Jun 18 08:19:28 UTC 2008,
1070
+ # :aws_status => "in-use",
1071
+ # :aws_id => "vol-60957009",
1072
+ # :aws_created_at => Wed Jun 18 08:19:20s UTC 2008,
1073
+ # :aws_instance_id => "i-c014c0a9"},
1074
+ # {:aws_size => 1,
1075
+ # :zone => "merlot",
1076
+ # :snapshot_id => nil,
1077
+ # :aws_status => "available",
1078
+ # :aws_id => "vol-58957031",
1079
+ # :aws_created_at => Wed Jun 18 08:19:21 UTC 2008,}, ... ]
1080
+ #
1081
+ def describe_volumes(list=[])
1082
+ link = generate_request("DescribeVolumes",
1083
+ amazonize_list('VolumeId',list.to_a))
1084
+ request_cache_or_info :describe_volumes, link, QEc2DescribeVolumesParser, @@bench, list.blank?
1085
+ rescue Exception
1086
+ on_exception
1087
+ end
1088
+
1089
+ # Create new EBS volume based on previously created snapshot.
1090
+ # +Size+ in Gigabytes.
1091
+ #
1092
+ # ec2.create_volume('snap-000000', 10, zone) #=>
1093
+ # {:snapshot_id => "snap-e21df98b",
1094
+ # :aws_status => "creating",
1095
+ # :aws_id => "vol-fc9f7a95",
1096
+ # :zone => "merlot",
1097
+ # :aws_created_at => Tue Jun 24 18:13:32 UTC 2008,
1098
+ # :aws_size => 94}
1099
+ #
1100
+ def create_volume(snapshot_id, size, zone)
1101
+ hash = { "Size" => size.to_s,
1102
+ "AvailabilityZone" => zone.to_s }
1103
+ # Get rig of empty snapshot: e8s guys do not like it
1104
+ hash["SnapshotId"] = snapshot_id.to_s unless snapshot_id.blank?
1105
+ link = generate_request("CreateVolume", hash )
1106
+ request_info(link, QEc2CreateVolumeParser.new(:logger => @logger))
1107
+ rescue Exception
1108
+ on_exception
1109
+ end
1110
+
1111
+ # Delete the specified EBS volume.
1112
+ # This does not deletes any snapshots created from this volume.
1113
+ #
1114
+ # ec2.delete_volume('vol-b48a6fdd') #=> true
1115
+ #
1116
+ def delete_volume(volume_id)
1117
+ link = generate_request("DeleteVolume",
1118
+ "VolumeId" => volume_id.to_s)
1119
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
1120
+ rescue Exception
1121
+ on_exception
1122
+ end
1123
+
1124
+ # Attach the specified EBS volume to a specified instance, exposing the
1125
+ # volume using the specified device name.
1126
+ #
1127
+ # ec2.attach_volume('vol-898a6fe0', 'i-7c905415', '/dev/sdh') #=>
1128
+ # { :aws_instance_id => "i-7c905415",
1129
+ # :aws_device => "/dev/sdh",
1130
+ # :aws_status => "attaching",
1131
+ # :aws_attached_at => "2008-03-28T14:14:39.000Z",
1132
+ # :aws_id => "vol-898a6fe0" }
1133
+ #
1134
+ def attach_volume(volume_id, instance_id, device)
1135
+ link = generate_request("AttachVolume",
1136
+ "VolumeId" => volume_id.to_s,
1137
+ "InstanceId" => instance_id.to_s,
1138
+ "Device" => device.to_s)
1139
+ request_info(link, QEc2AttachAndDetachVolumeParser.new(:logger => @logger))
1140
+ rescue Exception
1141
+ on_exception
1142
+ end
1143
+
1144
+ # Detach the specified EBS volume from the instance to which it is attached.
1145
+ #
1146
+ # ec2.detach_volume('vol-898a6fe0') #=>
1147
+ # { :aws_instance_id => "i-7c905415",
1148
+ # :aws_device => "/dev/sdh",
1149
+ # :aws_status => "detaching",
1150
+ # :aws_attached_at => "2008-03-28T14:38:34.000Z",
1151
+ # :aws_id => "vol-898a6fe0"}
1152
+ #
1153
+ def detach_volume(volume_id, instance_id=nil, device=nil, force=nil)
1154
+ hash = { "VolumeId" => volume_id.to_s }
1155
+ hash["InstanceId"] = instance_id.to_s unless instance_id.blank?
1156
+ hash["Device"] = device.to_s unless device.blank?
1157
+ hash["Force"] = 'true' if force
1158
+ #
1159
+ link = generate_request("DetachVolume", hash)
1160
+ request_info(link, QEc2AttachAndDetachVolumeParser.new(:logger => @logger))
1161
+ rescue Exception
1162
+ on_exception
1163
+ end
1164
+
1165
+
1166
+ #-----------------------------------------------------------------
1167
+ # EBS: Snapshots
1168
+ #-----------------------------------------------------------------
1169
+
1170
+ # Describe all EBS snapshots.
1171
+ #
1172
+ # ec2.describe_snapshots #=>
1173
+ # [ { :aws_progress => "100%",
1174
+ # :aws_status => "completed",
1175
+ # :aws_id => "snap-72a5401b",
1176
+ # :aws_volume_id => "vol-5582673c",
1177
+ # :aws_started_at => "2008-02-23T02:50:48.000Z"},
1178
+ # { :aws_progress => "100%",
1179
+ # :aws_status => "completed",
1180
+ # :aws_id => "snap-75a5401c",
1181
+ # :aws_volume_id => "vol-5582673c",
1182
+ # :aws_started_at => "2008-02-23T16:23:19.000Z" },...]
1183
+ #
1184
+ def describe_snapshots(list=[])
1185
+ link = generate_request("DescribeSnapshots",
1186
+ amazonize_list('SnapshotId',list.to_a))
1187
+ request_cache_or_info :describe_snapshots, link, QEc2DescribeSnapshotsParser, @@bench, list.blank?
1188
+ rescue Exception
1189
+ on_exception
1190
+ end
1191
+
1192
+ # Create a snapshot of specified volume.
1193
+ #
1194
+ # ec2.create_snapshot('vol-898a6fe0') #=>
1195
+ # {:aws_volume_id => "vol-fd9f7a94",
1196
+ # :aws_started_at => Tue Jun 24 18:40:40 UTC 2008,
1197
+ # :aws_progress => "",
1198
+ # :aws_status => "pending",
1199
+ # :aws_id => "snap-d56783bc"}
1200
+ #
1201
+ def create_snapshot(volume_id)
1202
+ link = generate_request("CreateSnapshot",
1203
+ "VolumeId" => volume_id.to_s)
1204
+ request_info(link, QEc2CreateSnapshotParser.new(:logger => @logger))
1205
+ rescue Exception
1206
+ on_exception
1207
+ end
1208
+
1209
+ # Create a snapshot of specified volume, but with the normal retry algorithms disabled.
1210
+ # This method will return immediately upon error. The user can specify connect and read timeouts (in s)
1211
+ # for the connection to AWS. If the user does not specify timeouts, try_create_snapshot uses the default values
1212
+ # in Rightscale::HttpConnection.
1213
+ #
1214
+ # ec2.try_create_snapshot('vol-898a6fe0') #=>
1215
+ # {:aws_volume_id => "vol-fd9f7a94",
1216
+ # :aws_started_at => Tue Jun 24 18:40:40 UTC 2008,
1217
+ # :aws_progress => "",
1218
+ # :aws_status => "pending",
1219
+ # :aws_id => "snap-d56783bc"}
1220
+ #
1221
+ def try_create_snapshot(volume_id, connect_timeout = nil, read_timeout = nil)
1222
+ # For safety in the ensure block...we don't want to restore values
1223
+ # if we never read them in the first place
1224
+ orig_reiteration_time = nil
1225
+ orig_http_params = nil
1226
+
1227
+ orig_reiteration_time = RightAws::AWSErrorHandler::reiteration_time
1228
+ RightAws::AWSErrorHandler::reiteration_time = 0
1229
+
1230
+ orig_http_params = Rightscale::HttpConnection::params()
1231
+ new_http_params = orig_http_params.dup
1232
+ new_http_params[:http_connection_retry_count] = 0
1233
+ new_http_params[:http_connection_open_timeout] = connect_timeout if !connect_timeout.nil?
1234
+ new_http_params[:http_connection_read_timeout] = read_timeout if !read_timeout.nil?
1235
+ Rightscale::HttpConnection::params = new_http_params
1236
+
1237
+ link = generate_request("CreateSnapshot",
1238
+ "VolumeId" => volume_id.to_s)
1239
+ request_info(link, QEc2CreateSnapshotParser.new(:logger => @logger))
1240
+
1241
+ rescue Exception
1242
+ on_exception
1243
+ ensure
1244
+ RightAws::AWSErrorHandler::reiteration_time = orig_reiteration_time if orig_reiteration_time
1245
+ Rightscale::HttpConnection::params = orig_http_params if orig_http_params
1246
+ end
1247
+
1248
+ # Delete the specified snapshot.
1249
+ #
1250
+ # ec2.delete_snapshot('snap-55a5403c') #=> true
1251
+ #
1252
+ def delete_snapshot(snapshot_id)
1253
+ link = generate_request("DeleteSnapshot",
1254
+ "SnapshotId" => snapshot_id.to_s)
1255
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
1256
+ rescue Exception
1257
+ on_exception
1258
+ end
1259
+
1260
+ #-----------------------------------------------------------------
1261
+ # PARSERS: Boolean Response Parser
1262
+ #-----------------------------------------------------------------
1263
+
1264
+ class RightBoolResponseParser < RightAWSParser #:nodoc:
1265
+ def tagend(name)
1266
+ @result = @text=='true' ? true : false if name == 'return'
1267
+ end
1268
+ end
1269
+
1270
+ #-----------------------------------------------------------------
1271
+ # PARSERS: Key Pair
1272
+ #-----------------------------------------------------------------
1273
+
1274
+ class QEc2DescribeKeyPairParser < RightAWSParser #:nodoc:
1275
+ def tagstart(name, attributes)
1276
+ @item = {} if name == 'item'
1277
+ end
1278
+ def tagend(name)
1279
+ case name
1280
+ when 'keyName' then @item[:aws_key_name] = @text
1281
+ when 'keyFingerprint' then @item[:aws_fingerprint] = @text
1282
+ when 'item' then @result << @item
1283
+ end
1284
+ end
1285
+ def reset
1286
+ @result = [];
1287
+ end
1288
+ end
1289
+
1290
+ class QEc2CreateKeyPairParser < RightAWSParser #:nodoc:
1291
+ def tagstart(name, attributes)
1292
+ @result = {} if name == 'CreateKeyPairResponse'
1293
+ end
1294
+ def tagend(name)
1295
+ case name
1296
+ when 'keyName' then @result[:aws_key_name] = @text
1297
+ when 'keyFingerprint' then @result[:aws_fingerprint] = @text
1298
+ when 'keyMaterial' then @result[:aws_material] = @text
1299
+ end
1300
+ end
1301
+ end
1302
+
1303
+ #-----------------------------------------------------------------
1304
+ # PARSERS: Security Groups
1305
+ #-----------------------------------------------------------------
1306
+
1307
+ class QEc2UserIdGroupPairType #:nodoc:
1308
+ attr_accessor :userId
1309
+ attr_accessor :groupName
1310
+ end
1311
+
1312
+ class QEc2IpPermissionType #:nodoc:
1313
+ attr_accessor :ipProtocol
1314
+ attr_accessor :fromPort
1315
+ attr_accessor :toPort
1316
+ attr_accessor :groups
1317
+ attr_accessor :ipRanges
1318
+ end
1319
+
1320
+ class QEc2SecurityGroupItemType #:nodoc:
1321
+ attr_accessor :groupName
1322
+ attr_accessor :groupDescription
1323
+ attr_accessor :ownerId
1324
+ attr_accessor :ipPermissions
1325
+ end
1326
+
1327
+
1328
+ class QEc2DescribeSecurityGroupsParser < RightAWSParser #:nodoc:
1329
+ def tagstart(name, attributes)
1330
+ case name
1331
+ when 'item'
1332
+ if @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo'
1333
+ @group = QEc2SecurityGroupItemType.new
1334
+ @group.ipPermissions = []
1335
+ elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions'
1336
+ @perm = QEc2IpPermissionType.new
1337
+ @perm.ipRanges = []
1338
+ @perm.groups = []
1339
+ elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/groups'
1340
+ @sgroup = QEc2UserIdGroupPairType.new
1341
+ end
1342
+ end
1343
+ end
1344
+ def tagend(name)
1345
+ case name
1346
+ when 'ownerId' then @group.ownerId = @text
1347
+ when 'groupDescription' then @group.groupDescription = @text
1348
+ when 'groupName'
1349
+ if @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item'
1350
+ @group.groupName = @text
1351
+ elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/groups/item'
1352
+ @sgroup.groupName = @text
1353
+ end
1354
+ when 'ipProtocol' then @perm.ipProtocol = @text
1355
+ when 'fromPort' then @perm.fromPort = @text
1356
+ when 'toPort' then @perm.toPort = @text
1357
+ when 'userId' then @sgroup.userId = @text
1358
+ when 'cidrIp' then @perm.ipRanges << @text
1359
+ when 'item'
1360
+ if @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/groups'
1361
+ @perm.groups << @sgroup
1362
+ elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions'
1363
+ @group.ipPermissions << @perm
1364
+ elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo'
1365
+ @result << @group
1366
+ end
1367
+ end
1368
+ end
1369
+ def reset
1370
+ @result = []
1371
+ end
1372
+ end
1373
+
1374
+ #-----------------------------------------------------------------
1375
+ # PARSERS: Images
1376
+ #-----------------------------------------------------------------
1377
+
1378
+ class QEc2DescribeImagesParser < RightAWSParser #:nodoc:
1379
+ def tagstart(name, attributes)
1380
+ if name == 'item' && @xmlpath[%r{.*/imagesSet$}]
1381
+ @image = {}
1382
+ end
1383
+ end
1384
+ def tagend(name)
1385
+ case name
1386
+ when 'imageId' then @image[:aws_id] = @text
1387
+ when 'imageLocation' then @image[:aws_location] = @text
1388
+ when 'imageState' then @image[:aws_state] = @text
1389
+ when 'imageOwnerId' then @image[:aws_owner] = @text
1390
+ when 'isPublic' then @image[:aws_is_public]= @text == 'true' ? true : false
1391
+ when 'productCode' then (@image[:aws_product_codes] ||= []) << @text
1392
+ when 'architecture' then @image[:aws_architecture] = @text
1393
+ when 'imageType' then @image[:aws_image_type] = @text
1394
+ when 'kernelId' then @image[:aws_kernel_id] = @text
1395
+ when 'ramdiskId' then @image[:aws_ramdisk_id] = @text
1396
+ when 'item' then @result << @image if @xmlpath[%r{.*/imagesSet$}]
1397
+ end
1398
+ end
1399
+ def reset
1400
+ @result = []
1401
+ end
1402
+ end
1403
+
1404
+ class QEc2RegisterImageParser < RightAWSParser #:nodoc:
1405
+ def tagend(name)
1406
+ @result = @text if name == 'imageId'
1407
+ end
1408
+ end
1409
+
1410
+ #-----------------------------------------------------------------
1411
+ # PARSERS: Image Attribute
1412
+ #-----------------------------------------------------------------
1413
+
1414
+ class QEc2DescribeImageAttributeParser < RightAWSParser #:nodoc:
1415
+ def tagstart(name, attributes)
1416
+ case name
1417
+ when 'launchPermission'
1418
+ @result[:groups] = []
1419
+ @result[:users] = []
1420
+ when 'productCodes'
1421
+ @result[:aws_product_codes] = []
1422
+ end
1423
+ end
1424
+ def tagend(name)
1425
+ # right now only 'launchPermission' is supported by Amazon.
1426
+ # But nobody know what will they xml later as attribute. That is why we
1427
+ # check for 'group' and 'userId' inside of 'launchPermission/item'
1428
+ case name
1429
+ when 'imageId' then @result[:aws_id] = @text
1430
+ when 'group' then @result[:groups] << @text if @xmlpath == 'DescribeImageAttributeResponse/launchPermission/item'
1431
+ when 'userId' then @result[:users] << @text if @xmlpath == 'DescribeImageAttributeResponse/launchPermission/item'
1432
+ when 'productCode' then @result[:aws_product_codes] << @text
1433
+ when 'kernel' then @result[:aws_kernel] = @text
1434
+ when 'ramdisk' then @result[:aws_ramdisk] = @text
1435
+ when 'blockDeviceMapping' then @result[:block_device_mapping] = @text
1436
+ end
1437
+ end
1438
+ def reset
1439
+ @result = {}
1440
+ end
1441
+ end
1442
+
1443
+ #-----------------------------------------------------------------
1444
+ # PARSERS: Instances
1445
+ #-----------------------------------------------------------------
1446
+
1447
+ class QEc2DescribeInstancesParser < RightAWSParser #:nodoc:
1448
+ def tagstart(name, attributes)
1449
+ # DescribeInstances property
1450
+ if (name == 'item' && @xmlpath == 'DescribeInstancesResponse/reservationSet') ||
1451
+ # RunInstances property
1452
+ (name == 'RunInstancesResponse')
1453
+ @reservation = { :aws_groups => [],
1454
+ :instances_set => [] }
1455
+
1456
+ elsif (name == 'item') &&
1457
+ # DescribeInstances property
1458
+ ( @xmlpath=='DescribeInstancesResponse/reservationSet/item/instancesSet' ||
1459
+ # RunInstances property
1460
+ @xmlpath=='RunInstancesResponse/instancesSet' )
1461
+ # the optional params (sometimes are missing and we dont want them to be nil)
1462
+ @instance = { :aws_reason => '',
1463
+ :dns_name => '',
1464
+ :private_dns_name => '',
1465
+ :ami_launch_index => '',
1466
+ :ssh_key_name => '',
1467
+ :aws_state => '',
1468
+ :aws_product_codes => [] }
1469
+ end
1470
+ end
1471
+ def tagend(name)
1472
+ case name
1473
+ # reservation
1474
+ when 'reservationId' then @reservation[:aws_reservation_id] = @text
1475
+ when 'ownerId' then @reservation[:aws_owner] = @text
1476
+ when 'groupId' then @reservation[:aws_groups] << @text
1477
+ # instance
1478
+ when 'instanceId' then @instance[:aws_instance_id] = @text
1479
+ when 'imageId' then @instance[:aws_image_id] = @text
1480
+ when 'dnsName' then @instance[:dns_name] = @text
1481
+ when 'privateDnsName' then @instance[:private_dns_name] = @text
1482
+ when 'reason' then @instance[:aws_reason] = @text
1483
+ when 'keyName' then @instance[:ssh_key_name] = @text
1484
+ when 'amiLaunchIndex' then @instance[:ami_launch_index] = @text
1485
+ when 'code' then @instance[:aws_state_code] = @text
1486
+ when 'name' then @instance[:aws_state] = @text
1487
+ when 'productCode' then @instance[:aws_product_codes] << @text
1488
+ when 'instanceType' then @instance[:aws_instance_type] = @text
1489
+ when 'launchTime' then @instance[:aws_launch_time] = @text
1490
+ when 'kernelId' then @instance[:aws_kernel_id] = @text
1491
+ when 'ramdiskId' then @instance[:aws_ramdisk_id] = @text
1492
+ when 'platform' then @instance[:aws_platform] = @text
1493
+ when 'availabilityZone' then @instance[:aws_availability_zone] = @text
1494
+ when 'item'
1495
+ if @xmlpath == 'DescribeInstancesResponse/reservationSet/item/instancesSet' || # DescribeInstances property
1496
+ @xmlpath == 'RunInstancesResponse/instancesSet' # RunInstances property
1497
+ @reservation[:instances_set] << @instance
1498
+ elsif @xmlpath=='DescribeInstancesResponse/reservationSet' # DescribeInstances property
1499
+ @result << @reservation
1500
+ end
1501
+ when 'RunInstancesResponse' then @result << @reservation # RunInstances property
1502
+ end
1503
+ end
1504
+ def reset
1505
+ @result = []
1506
+ end
1507
+ end
1508
+
1509
+ class QEc2ConfirmProductInstanceParser < RightAWSParser #:nodoc:
1510
+ def tagend(name)
1511
+ @result = @text if name == 'ownerId'
1512
+ end
1513
+ end
1514
+
1515
+ class QEc2TerminateInstancesParser < RightAWSParser #:nodoc:
1516
+ def tagstart(name, attributes)
1517
+ @instance = {} if name == 'item'
1518
+ end
1519
+ def tagend(name)
1520
+ case name
1521
+ when 'instanceId' then @instance[:aws_instance_id] = @text
1522
+ when 'code'
1523
+ if @xmlpath == 'TerminateInstancesResponse/instancesSet/item/shutdownState'
1524
+ @instance[:aws_shutdown_state_code] = @text.to_i
1525
+ else @instance[:aws_prev_state_code] = @text.to_i end
1526
+ when 'name'
1527
+ if @xmlpath == 'TerminateInstancesResponse/instancesSet/item/shutdownState'
1528
+ @instance[:aws_shutdown_state] = @text
1529
+ else @instance[:aws_prev_state] = @text end
1530
+ when 'item' then @result << @instance
1531
+ end
1532
+ end
1533
+ def reset
1534
+ @result = []
1535
+ end
1536
+ end
1537
+
1538
+ #-----------------------------------------------------------------
1539
+ # PARSERS: ReservedInstances
1540
+ #-----------------------------------------------------------------
1541
+
1542
+ class QEc2DescribeReservedInstancesParser < RightAWSParser #:nodoc:
1543
+ def tagstart(name, attributes)
1544
+ @item = {} if name == 'item'
1545
+ end
1546
+ def tagend(name)
1547
+ case name
1548
+ when 'reservedInstancesId' then @item[:aws_id] = @text
1549
+ when 'instanceType' then @item[:aws_instance_type] = @text
1550
+ when 'availabilityZone' then @item[:aws_availability_zone] = @text
1551
+ when 'duration' then @item[:aws_duration] = @text.to_i
1552
+ when 'usagePrice' then @item[:aws_usage_price] = @text.to_f
1553
+ when 'fixedPrice' then @item[:aws_fixed_price] = @text.to_f
1554
+ when 'instanceCount' then @item[:aws_instance_count] = @text.to_i
1555
+ when 'productDescription' then @item[:aws_product_description] = @text
1556
+ when 'state' then @item[:aws_state] = @text
1557
+ when 'item' then @result << @item
1558
+ end
1559
+ end
1560
+ def reset
1561
+ @result = []
1562
+ end
1563
+ end
1564
+
1565
+ class QEc2DescribeReservedInstancesOfferingsParser < RightAWSParser #:nodoc:
1566
+ def tagstart(name, attributes)
1567
+ @item = {} if name == 'item'
1568
+ end
1569
+ def tagend(name)
1570
+ case name
1571
+ when 'reservedInstancesOfferingId' then @item[:aws_id] = @text
1572
+ when 'instanceType' then @item[:aws_instance_type] = @text
1573
+ when 'availabilityZone' then @item[:aws_availability_zone] = @text
1574
+ when 'duration' then @item[:aws_duration] = @text.to_i
1575
+ when 'usagePrice' then @item[:aws_usage_price] = @text.to_f
1576
+ when 'fixedPrice' then @item[:aws_fixed_price] = @text.to_f
1577
+ when 'productDescription' then @item[:aws_product_description] = @text
1578
+ when 'item' then @result << @item
1579
+ end
1580
+ end
1581
+ def reset
1582
+ @result = []
1583
+ end
1584
+ end
1585
+
1586
+ class QEc2PurchaseReservedInstancesOfferingParser < RightAWSParser #:nodoc:
1587
+ def tagend(name)
1588
+ if name == 'reservedInstancesId'
1589
+ @result = @text
1590
+ end
1591
+ end
1592
+ def reset
1593
+ @result = ''
1594
+ end
1595
+ end
1596
+
1597
+ #-----------------------------------------------------------------
1598
+ # PARSERS: Console
1599
+ #-----------------------------------------------------------------
1600
+
1601
+ class QEc2GetConsoleOutputParser < RightAWSParser #:nodoc:
1602
+ def tagend(name)
1603
+ case name
1604
+ when 'instanceId' then @result[:aws_instance_id] = @text
1605
+ when 'timestamp' then @result[:aws_timestamp] = @text
1606
+ @result[:timestamp] = (Time.parse(@text)).utc
1607
+ when 'output' then @result[:aws_output] = Base64.decode64(@text)
1608
+ end
1609
+ end
1610
+ def reset
1611
+ @result = {}
1612
+ end
1613
+ end
1614
+
1615
+ #-----------------------------------------------------------------
1616
+ # Instances: Wondows related part
1617
+ #-----------------------------------------------------------------
1618
+ class QEc2DescribeBundleTasksParser < RightAWSParser #:nodoc:
1619
+ def tagstart(name, attributes)
1620
+ @bundle = {} if name == 'item'
1621
+ end
1622
+ def tagend(name)
1623
+ case name
1624
+ # when 'requestId' then @bundle[:request_id] = @text
1625
+ when 'instanceId' then @bundle[:aws_instance_id] = @text
1626
+ when 'bundleId' then @bundle[:aws_id] = @text
1627
+ when 'bucket' then @bundle[:s3_bucket] = @text
1628
+ when 'prefix' then @bundle[:s3_prefix] = @text
1629
+ when 'startTime' then @bundle[:aws_start_time] = @text
1630
+ when 'updateTime' then @bundle[:aws_update_time] = @text
1631
+ when 'state' then @bundle[:aws_state] = @text
1632
+ when 'progress' then @bundle[:aws_progress] = @text
1633
+ when 'code' then @bundle[:aws_error_code] = @text
1634
+ when 'message' then @bundle[:aws_error_message] = @text
1635
+ when 'item' then @result << @bundle
1636
+ end
1637
+ end
1638
+ def reset
1639
+ @result = []
1640
+ end
1641
+ end
1642
+
1643
+ class QEc2BundleInstanceParser < RightAWSParser #:nodoc:
1644
+ def tagend(name)
1645
+ case name
1646
+ # when 'requestId' then @result[:request_id] = @text
1647
+ when 'instanceId' then @result[:aws_instance_id] = @text
1648
+ when 'bundleId' then @result[:aws_id] = @text
1649
+ when 'bucket' then @result[:s3_bucket] = @text
1650
+ when 'prefix' then @result[:s3_prefix] = @text
1651
+ when 'startTime' then @result[:aws_start_time] = @text
1652
+ when 'updateTime' then @result[:aws_update_time] = @text
1653
+ when 'state' then @result[:aws_state] = @text
1654
+ when 'progress' then @result[:aws_progress] = @text
1655
+ when 'code' then @result[:aws_error_code] = @text
1656
+ when 'message' then @result[:aws_error_message] = @text
1657
+ end
1658
+ end
1659
+ def reset
1660
+ @result = {}
1661
+ end
1662
+ end
1663
+
1664
+ #-----------------------------------------------------------------
1665
+ # PARSERS: Elastic IPs
1666
+ #-----------------------------------------------------------------
1667
+
1668
+ class QEc2AllocateAddressParser < RightAWSParser #:nodoc:
1669
+ def tagend(name)
1670
+ @result = @text if name == 'publicIp'
1671
+ end
1672
+ end
1673
+
1674
+ class QEc2DescribeAddressesParser < RightAWSParser #:nodoc:
1675
+ def tagstart(name, attributes)
1676
+ @address = {} if name == 'item'
1677
+ end
1678
+ def tagend(name)
1679
+ case name
1680
+ when 'instanceId' then @address[:instance_id] = @text.blank? ? nil : @text
1681
+ when 'publicIp' then @address[:public_ip] = @text
1682
+ when 'item' then @result << @address
1683
+ end
1684
+ end
1685
+ def reset
1686
+ @result = []
1687
+ end
1688
+ end
1689
+
1690
+ #-----------------------------------------------------------------
1691
+ # PARSERS: AvailabilityZones
1692
+ #-----------------------------------------------------------------
1693
+
1694
+ class QEc2DescribeAvailabilityZonesParser < RightAWSParser #:nodoc:
1695
+ def tagstart(name, attributes)
1696
+ @zone = {} if name == 'item'
1697
+ end
1698
+ def tagend(name)
1699
+ case name
1700
+ when 'regionName' then @zone[:region_name] = @text
1701
+ when 'zoneName' then @zone[:zone_name] = @text
1702
+ when 'zoneState' then @zone[:zone_state] = @text
1703
+ when 'item' then @result << @zone
1704
+ end
1705
+ end
1706
+ def reset
1707
+ @result = []
1708
+ end
1709
+ end
1710
+
1711
+ #-----------------------------------------------------------------
1712
+ # PARSERS: Regions
1713
+ #-----------------------------------------------------------------
1714
+
1715
+ class QEc2DescribeRegionsParser < RightAWSParser #:nodoc:
1716
+ def tagend(name)
1717
+ @result << @text if name == 'regionName'
1718
+ end
1719
+ def reset
1720
+ @result = []
1721
+ end
1722
+ end
1723
+
1724
+ #-----------------------------------------------------------------
1725
+ # PARSERS: EBS - Volumes
1726
+ #-----------------------------------------------------------------
1727
+
1728
+ class QEc2CreateVolumeParser < RightAWSParser #:nodoc:
1729
+ def tagend(name)
1730
+ case name
1731
+ when 'volumeId' then @result[:aws_id] = @text
1732
+ when 'status' then @result[:aws_status] = @text
1733
+ when 'createTime' then @result[:aws_created_at] = Time.parse(@text)
1734
+ when 'size' then @result[:aws_size] = @text.to_i ###
1735
+ when 'snapshotId' then @result[:snapshot_id] = @text.blank? ? nil : @text ###
1736
+ when 'availabilityZone' then @result[:zone] = @text ###
1737
+ end
1738
+ end
1739
+ def reset
1740
+ @result = {}
1741
+ end
1742
+ end
1743
+
1744
+ class QEc2AttachAndDetachVolumeParser < RightAWSParser #:nodoc:
1745
+ def tagend(name)
1746
+ case name
1747
+ when 'volumeId' then @result[:aws_id] = @text
1748
+ when 'instanceId' then @result[:aws_instance_id] = @text
1749
+ when 'device' then @result[:aws_device] = @text
1750
+ when 'status' then @result[:aws_attachment_status] = @text
1751
+ when 'attachTime' then @result[:aws_attached_at] = Time.parse(@text)
1752
+ end
1753
+ end
1754
+ def reset
1755
+ @result = {}
1756
+ end
1757
+ end
1758
+
1759
+ class QEc2DescribeVolumesParser < RightAWSParser #:nodoc:
1760
+ def tagstart(name, attributes)
1761
+ case name
1762
+ when 'item'
1763
+ case @xmlpath
1764
+ when 'DescribeVolumesResponse/volumeSet' then @volume = {}
1765
+ end
1766
+ end
1767
+ end
1768
+ def tagend(name)
1769
+ case name
1770
+ when 'volumeId'
1771
+ case @xmlpath
1772
+ when 'DescribeVolumesResponse/volumeSet/item' then @volume[:aws_id] = @text
1773
+ end
1774
+ when 'status'
1775
+ case @xmlpath
1776
+ when 'DescribeVolumesResponse/volumeSet/item' then @volume[:aws_status] = @text
1777
+ when 'DescribeVolumesResponse/volumeSet/item/attachmentSet/item' then @volume[:aws_attachment_status] = @text
1778
+ end
1779
+ when 'size' then @volume[:aws_size] = @text.to_i
1780
+ when 'createTime' then @volume[:aws_created_at] = Time.parse(@text)
1781
+ when 'instanceId' then @volume[:aws_instance_id] = @text
1782
+ when 'device' then @volume[:aws_device] = @text
1783
+ when 'attachTime' then @volume[:aws_attached_at] = Time.parse(@text)
1784
+ when 'snapshotId' then @volume[:snapshot_id] = @text.blank? ? nil : @text
1785
+ when 'availabilityZone' then @volume[:zone] = @text
1786
+ when 'item'
1787
+ case @xmlpath
1788
+ when 'DescribeVolumesResponse/volumeSet' then @result << @volume
1789
+ end
1790
+ end
1791
+ end
1792
+ def reset
1793
+ @result = []
1794
+ end
1795
+ end
1796
+
1797
+ #-----------------------------------------------------------------
1798
+ # PARSERS: EBS - Snapshots
1799
+ #-----------------------------------------------------------------
1800
+
1801
+ class QEc2DescribeSnapshotsParser < RightAWSParser #:nodoc:
1802
+ def tagstart(name, attributes)
1803
+ @snapshot = {} if name == 'item'
1804
+ end
1805
+ def tagend(name)
1806
+ case name
1807
+ when 'volumeId' then @snapshot[:aws_volume_id] = @text
1808
+ when 'snapshotId' then @snapshot[:aws_id] = @text
1809
+ when 'status' then @snapshot[:aws_status] = @text
1810
+ when 'startTime' then @snapshot[:aws_started_at] = Time.parse(@text)
1811
+ when 'progress' then @snapshot[:aws_progress] = @text
1812
+ when 'item' then @result << @snapshot
1813
+ end
1814
+ end
1815
+ def reset
1816
+ @result = []
1817
+ end
1818
+ end
1819
+
1820
+ class QEc2CreateSnapshotParser < RightAWSParser #:nodoc:
1821
+ def tagend(name)
1822
+ case name
1823
+ when 'volumeId' then @result[:aws_volume_id] = @text
1824
+ when 'snapshotId' then @result[:aws_id] = @text
1825
+ when 'status' then @result[:aws_status] = @text
1826
+ when 'startTime' then @result[:aws_started_at] = Time.parse(@text)
1827
+ when 'progress' then @result[:aws_progress] = @text
1828
+ end
1829
+ end
1830
+ def reset
1831
+ @result = {}
1832
+ end
1833
+ end
1834
+
1835
+ end
1836
+
1837
+ end