kerryb-right_aws 1.7.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,1223 @@
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.
29
+ # For explanations of the semantics
30
+ # of each call, please refer to Amazon's documentation at
31
+ # http://developer.amazonwebservices.com/connect/kbcategory.jspa?categoryID=87
32
+ #
33
+ # Examples:
34
+ #
35
+ # Create an EC2 interface handle:
36
+ #
37
+ # @ec2 = RightAws::Ec2.new(aws_access_key_id,
38
+ # aws_secret_access_key)
39
+ # Create a new SSH key pair:
40
+ # @key = 'right_ec2_awesome_test_key'
41
+ # new_key = @ec2.create_key_pair(@key)
42
+ # keys = @ec2.describe_key_pairs
43
+ #
44
+ # Create a security group:
45
+ # @group = 'right_ec2_awesome_test_security_group'
46
+ # @ec2.create_security_group(@group,'My awesome test group')
47
+ # group = @ec2.describe_security_groups([@group])[0]
48
+ #
49
+ # Configure a security group:
50
+ # @ec2.authorize_security_group_named_ingress(@group, account_number, 'default')
51
+ # @ec2.authorize_security_group_IP_ingress(@group, 80,80,'udp','192.168.1.0/8')
52
+ #
53
+ # Describe the available images:
54
+ # images = @ec2.describe_images
55
+ #
56
+ # Launch an instance:
57
+ # ec2.run_instances('ami-9a9e7bf3', 1, 1, ['default'], @key, 'SomeImportantUserData', 'public')
58
+ #
59
+ #
60
+ # Describe running instances:
61
+ # @ec2.describe_instances
62
+ #
63
+ # Error handling: all operations raise an RightAws::AwsError in case
64
+ # of problems. Note that transient errors are automatically retried.
65
+
66
+ class Ec2 < RightAwsBase
67
+ include RightAwsBaseInterface
68
+
69
+ # Amazon EC2 API version being used
70
+ API_VERSION = "2008-02-01"
71
+ DEFAULT_HOST = "ec2.amazonaws.com"
72
+ DEFAULT_PATH = '/'
73
+ DEFAULT_PROTOCOL = 'https'
74
+ DEFAULT_PORT = 443
75
+
76
+ # Default addressing type (public=NAT, direct=no-NAT) used when launching instances.
77
+ DEFAULT_ADDRESSING_TYPE = 'public'
78
+ DNS_ADDRESSING_SET = ['public','direct']
79
+
80
+ # Amazon EC2 Instance Types : http://www.amazon.com/b?ie=UTF8&node=370375011
81
+ # Default EC2 instance type (platform)
82
+ DEFAULT_INSTANCE_TYPE = 'm1.small'
83
+ INSTANCE_TYPES = ['m1.small','c1.medium','m1.large','m1.xlarge','c1.xlarge']
84
+
85
+ @@bench = AwsBenchmarkingBlock.new
86
+ def self.bench_xml
87
+ @@bench.xml
88
+ end
89
+ def self.bench_ec2
90
+ @@bench.service
91
+ end
92
+
93
+ # Current API version (sometimes we have to check it outside the GEM).
94
+ @@api = ENV['EC2_API_VERSION'] || API_VERSION
95
+ def self.api
96
+ @@api
97
+ end
98
+
99
+ # Create a new handle to an EC2 account. All handles share the same per process or per thread
100
+ # HTTP connection to Amazon EC2. Each handle is for a specific account. The params have the
101
+ # following options:
102
+ # * <tt>:server</tt>: EC2 service host, default: DEFAULT_HOST
103
+ # * <tt>:port</tt>: EC2 service port, default: DEFAULT_PORT
104
+ # * <tt>:protocol</tt>: 'http' or 'https', default: DEFAULT_PROTOCOL
105
+ # * <tt>:multi_thread</tt>: true=HTTP connection per thread, false=per process
106
+ # * <tt>:logger</tt>: for log messages, default: RAILS_DEFAULT_LOGGER else STDOUT
107
+ # * <tt>:signature_version</tt>: The signature version : '0' or '1'(default)
108
+ #
109
+ def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
110
+ init({ :name => 'EC2',
111
+ :default_host => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).host : DEFAULT_HOST,
112
+ :default_port => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).port : DEFAULT_PORT,
113
+ :default_service => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).path : DEFAULT_PATH,
114
+ :default_protocol => ENV['EC2_URL'] ? URI.parse(ENV['EC2_URL']).scheme : DEFAULT_PROTOCOL },
115
+ aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'] ,
116
+ aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'],
117
+ params)
118
+ end
119
+
120
+
121
+ def generate_request(action, params={}) #:nodoc:
122
+ service_hash = {"Action" => action,
123
+ "AWSAccessKeyId" => @aws_access_key_id,
124
+ "Version" => @@api,
125
+ "Timestamp" => Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.000Z"),
126
+ "SignatureVersion" => signature_version }
127
+ service_hash.update(params)
128
+ # prepare string to sight
129
+ string_to_sign = case signature_version
130
+ when '0' then service_hash["Action"] + service_hash["Timestamp"]
131
+ when '1' then service_hash.sort{|a,b| (a[0].to_s.downcase)<=>(b[0].to_s.downcase)}.to_s
132
+ end
133
+ service_hash.update('Signature' => AwsUtils::sign(@aws_secret_access_key, string_to_sign))
134
+ request_params = service_hash.to_a.collect{|key,val| key + "=" + CGI::escape(val) }.join("&")
135
+ request = Net::HTTP::Get.new("#{@params[:service]}?#{request_params}")
136
+ # prepare output hash
137
+ { :request => request,
138
+ :server => @params[:server],
139
+ :port => @params[:port],
140
+ :protocol => @params[:protocol],
141
+ :proxy => @params[:proxy] }
142
+ end
143
+
144
+ # Sends request to Amazon and parses the response
145
+ # Raises AwsError if any banana happened
146
+ def request_info(request, parser) #:nodoc:
147
+ thread = @params[:multi_thread] ? Thread.current : Thread.main
148
+ thread[:ec2_connection] ||= Rightscale::HttpConnection.new(:exception => AwsError, :logger => @logger)
149
+ request_info_impl(thread[:ec2_connection], @@bench, request, parser)
150
+ end
151
+
152
+ def request_cache_or_info(method, link, parser_class, use_cache=true) #:nodoc:
153
+ # We do not want to break the logic of parsing hence will use a dummy parser to process all the standart
154
+ # steps (errors checking etc). The dummy parser does nothig - just returns back the params it received.
155
+ # If the caching is enabled and hit then throw AwsNoChange.
156
+ # P.S. caching works for the whole images list only! (when the list param is blank) response, params = request_info(link, QEc2DummyParser.new)
157
+ # check cache
158
+ response, params = request_info(link, QEc2DummyParser.new)
159
+ cache_hits?(method.to_sym, response.body) if use_cache
160
+ parser = parser_class.new(:logger => @logger)
161
+ @@bench.xml.add!{ parser.parse(response, params) }
162
+ result = block_given? ? yield(parser) : parser.result
163
+ # update parsed data
164
+ update_cache(method.to_sym, :parsed => result) if use_cache
165
+ result
166
+ end
167
+
168
+ def hash_params(prefix, list) #:nodoc:
169
+ groups = {}
170
+ list.each_index{|i| groups.update("#{prefix}.#{i+1}"=>list[i])} if list
171
+ return groups
172
+ end
173
+
174
+ #-----------------------------------------------------------------
175
+ # Images
176
+ #-----------------------------------------------------------------
177
+
178
+ def ec2_describe_images(list, list_by='ImageId', image_type=nil) #:nodoc:
179
+ request_hash = hash_params(list_by, list.to_a)
180
+ request_hash['ImageType'] = image_type if image_type
181
+ link = generate_request("DescribeImages", request_hash)
182
+ request_cache_or_info :describe_images, link, QEc2DescribeImagesParser, (list.blank? && list_by == 'ImageId' && image_type.blank?)
183
+ rescue Exception
184
+ on_exception
185
+ end
186
+
187
+ # Retrieve a list of images. Returns array of hashes describing the images or an exception:
188
+ # +image_type+ = 'machine' || 'kernel' || 'ramdisk'
189
+ #
190
+ # ec2.describe_images #=>
191
+ # [{:aws_owner => "522821470517",
192
+ # :aws_id => "ami-e4b6538d",
193
+ # :aws_state => "available",
194
+ # :aws_location => "marcins_cool_public_images/ubuntu-6.10.manifest.xml",
195
+ # :aws_is_public => true,
196
+ # :aws_architecture => "i386",
197
+ # :aws_image_type => "machine"},
198
+ # {...},
199
+ # {...} ]
200
+ #
201
+ # If +list+ param is set, then retrieve information about the listed images only:
202
+ #
203
+ # ec2.describe_images(['ami-e4b6538d']) #=>
204
+ # [{:aws_owner => "522821470517",
205
+ # :aws_id => "ami-e4b6538d",
206
+ # :aws_state => "available",
207
+ # :aws_location => "marcins_cool_public_images/ubuntu-6.10.manifest.xml",
208
+ # :aws_is_public => true,
209
+ # :aws_architecture => "i386",
210
+ # :aws_image_type => "machine"}]
211
+ #
212
+ def describe_images(list=[], image_type=nil)
213
+ ec2_describe_images(list, 'ImageId', image_type)
214
+ end
215
+
216
+ #
217
+ # Example:
218
+ #
219
+ # ec2.describe_images_by_owner('522821470517')
220
+ # ec2.describe_images_by_owner('self')
221
+ #
222
+ def describe_images_by_owner(list, image_type=nil)
223
+ ec2_describe_images(list, 'Owner', image_type)
224
+ end
225
+
226
+ #
227
+ # Example:
228
+ #
229
+ # ec2.describe_images_by_executable_by('522821470517')
230
+ # ec2.describe_images_by_executable_by('self')
231
+ #
232
+ def describe_images_by_executable_by(list, image_type=nil)
233
+ ec2_describe_images(list, 'ExecutableBy', image_type)
234
+ end
235
+
236
+
237
+ # Register new image at Amazon.
238
+ # Returns new image id or an exception.
239
+ #
240
+ # ec2.register_image('bucket/key/manifest') #=> 'ami-e444444d'
241
+ #
242
+ def register_image(image_location)
243
+ link = generate_request("RegisterImage",
244
+ 'ImageLocation' => image_location.to_s)
245
+ request_info(link, QEc2RegisterImageParser.new(:logger => @logger))
246
+ rescue Exception
247
+ on_exception
248
+ end
249
+
250
+ # Deregister image at Amazon. Returns +true+ or an exception.
251
+ #
252
+ # ec2.deregister_image('ami-e444444d') #=> true
253
+ #
254
+ def deregister_image(image_id)
255
+ link = generate_request("DeregisterImage",
256
+ 'ImageId' => image_id.to_s)
257
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
258
+ rescue Exception
259
+ on_exception
260
+ end
261
+
262
+
263
+ # Describe image attributes. Currently 'launchPermission', 'productCodes', 'kernel', 'ramdisk' and 'blockDeviceMapping' are supported.
264
+ #
265
+ # ec2.describe_image_attribute('ami-e444444d') #=> {:groups=>["all"], :users=>["000000000777"]}
266
+ #
267
+ def describe_image_attribute(image_id, attribute='launchPermission')
268
+ link = generate_request("DescribeImageAttribute",
269
+ 'ImageId' => image_id,
270
+ 'Attribute' => attribute)
271
+ request_info(link, QEc2DescribeImageAttributeParser.new(:logger => @logger))
272
+ rescue Exception
273
+ on_exception
274
+ end
275
+
276
+ # Reset image attribute. Currently, only 'launchPermission' is supported. Returns +true+ or an exception.
277
+ #
278
+ # ec2.reset_image_attribute('ami-e444444d') #=> true
279
+ #
280
+ def reset_image_attribute(image_id, attribute='launchPermission')
281
+ link = generate_request("ResetImageAttribute",
282
+ 'ImageId' => image_id,
283
+ 'Attribute' => attribute)
284
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
285
+ rescue Exception
286
+ on_exception
287
+ end
288
+
289
+ # Modify an image's attributes. It is recommended that you use
290
+ # modify_image_launch_perm_add_users, modify_image_launch_perm_remove_users, etc.
291
+ # instead of modify_image_attribute because the signature of
292
+ # modify_image_attribute may change with EC2 service changes.
293
+ #
294
+ # attribute : currently, only 'launchPermission' is supported.
295
+ # operation_type : currently, only 'add' & 'remove' are supported.
296
+ # vars:
297
+ # :user_group : currently, only 'all' is supported.
298
+ # :user_id
299
+ # :product_code
300
+ def modify_image_attribute(image_id, attribute, operation_type = nil, vars = {})
301
+ params = {'ImageId' => image_id,
302
+ 'Attribute' => attribute}
303
+ params['OperationType'] = operation_type if operation_type
304
+ params.update(hash_params('UserId', vars[:user_id].to_a)) if vars[:user_id]
305
+ params.update(hash_params('UserGroup', vars[:user_group].to_a)) if vars[:user_group]
306
+ params.update(hash_params('ProductCode', vars[:product_code])) if vars[:product_code]
307
+ link = generate_request("ModifyImageAttribute", params)
308
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
309
+ rescue Exception
310
+ on_exception
311
+ end
312
+
313
+ # Grant image launch permissions to users.
314
+ # Parameter +userId+ is a list of user AWS account ids.
315
+ # Returns +true+ or an exception.
316
+ #
317
+ # ec2.modify_image_launch_perm_add_users('ami-e444444d',['000000000777','000000000778']) #=> true
318
+ def modify_image_launch_perm_add_users(image_id, user_id=[])
319
+ modify_image_attribute(image_id, 'launchPermission', 'add', :user_id => user_id.to_a)
320
+ end
321
+
322
+ # Revokes image launch permissions for users. +userId+ is a list of users AWS accounts ids. Returns +true+ or an exception.
323
+ #
324
+ # ec2.modify_image_launch_perm_remove_users('ami-e444444d',['000000000777','000000000778']) #=> true
325
+ #
326
+ def modify_image_launch_perm_remove_users(image_id, user_id=[])
327
+ modify_image_attribute(image_id, 'launchPermission', 'remove', :user_id => user_id.to_a)
328
+ end
329
+
330
+ # Add image launch permissions for users groups (currently only 'all' is supported, which gives public launch permissions).
331
+ # Returns +true+ or an exception.
332
+ #
333
+ # ec2.modify_image_launch_perm_add_groups('ami-e444444d') #=> true
334
+ #
335
+ def modify_image_launch_perm_add_groups(image_id, user_group=['all'])
336
+ modify_image_attribute(image_id, 'launchPermission', 'add', :user_group => user_group.to_a)
337
+ end
338
+
339
+ # Remove image launch permissions for users groups (currently only 'all' is supported, which gives public launch permissions).
340
+ #
341
+ # ec2.modify_image_launch_perm_remove_groups('ami-e444444d') #=> true
342
+ #
343
+ def modify_image_launch_perm_remove_groups(image_id, user_group=['all'])
344
+ modify_image_attribute(image_id, 'launchPermission', 'remove', :user_group => user_group.to_a)
345
+ end
346
+
347
+ # Add product code to image
348
+ #
349
+ # ec2.modify_image_product_code('ami-e444444d','0ABCDEF') #=> true
350
+ #
351
+ def modify_image_product_code(image_id, product_code=[])
352
+ modify_image_attribute(image_id, 'productCodes', nil, :product_code => product_code.to_a)
353
+ end
354
+
355
+ #-----------------------------------------------------------------
356
+ # Instances
357
+ #-----------------------------------------------------------------
358
+
359
+ def get_desc_instances(instances) # :nodoc:
360
+ result = []
361
+ instances.each do |reservation|
362
+ reservation[:instances_set].each do |instance|
363
+ # Parse and remove timestamp from the reason string. The timestamp is of
364
+ # the request, not when EC2 took action, thus confusing & useless...
365
+ instance[:aws_reason] = instance[:aws_reason].sub(/\(\d[^)]*GMT\) */, '')
366
+ instance[:aws_owner] = reservation[:aws_owner]
367
+ instance[:aws_reservation_id] = reservation[:aws_reservation_id]
368
+ instance[:aws_groups] = reservation[:aws_groups]
369
+ result << instance
370
+ end
371
+ end
372
+ result
373
+ rescue Exception
374
+ on_exception
375
+ end
376
+
377
+ # Retrieve information about EC2 instances. If +list+ is omitted then returns the
378
+ # list of all instances.
379
+ #
380
+ # ec2.describe_instances #=>
381
+ # [{:aws_image_id => "ami-e444444d",
382
+ # :aws_reason => "",
383
+ # :aws_state_code => "16",
384
+ # :aws_owner => "000000000888",
385
+ # :aws_instance_id => "i-123f1234",
386
+ # :aws_reservation_id => "r-aabbccdd",
387
+ # :aws_state => "running",
388
+ # :dns_name => "domU-12-34-67-89-01-C9.usma2.compute.amazonaws.com",
389
+ # :ssh_key_name => "staging",
390
+ # :aws_groups => ["default"],
391
+ # :private_dns_name => "domU-12-34-67-89-01-C9.usma2.compute.amazonaws.com",
392
+ # :aws_instance_type => "m1.small",
393
+ # :aws_launch_time => "2008-1-1T00:00:00.000Z"},
394
+ # :aws_availability_zone => "us-east-1b",
395
+ # :aws_kernel_id => "aki-ba3adfd3",
396
+ # :aws_ramdisk_id => "ari-badbad00",
397
+ # ..., {...}]
398
+ #
399
+ def describe_instances(list=[])
400
+ link = generate_request("DescribeInstances", hash_params('InstanceId',list.to_a))
401
+ request_cache_or_info(:describe_instances, link, QEc2DescribeInstancesParser, list.blank?) do |parser|
402
+ get_desc_instances(parser.result)
403
+ end
404
+ rescue Exception
405
+ on_exception
406
+ end
407
+
408
+ # Return the product code attached to instance or +nil+ otherwise.
409
+ #
410
+ # ec2.confirm_product_instance('ami-e444444d','12345678') #=> nil
411
+ # ec2.confirm_product_instance('ami-e444444d','00001111') #=> "000000000888"
412
+ #
413
+ def confirm_product_instance(instance, product_code)
414
+ link = generate_request("ConfirmProductInstance", { 'ProductCode' => product_code,
415
+ 'InstanceId' => instance })
416
+ request_info(link, QEc2ConfirmProductInstanceParser.new(:logger => @logger))
417
+ end
418
+
419
+ # Launch new EC2 instances. Returns a list of launched instances or an exception.
420
+ #
421
+ # ec2.run_instances('ami-e444444d',1,1,['my_awesome_group'],'my_awesome_key', 'Woohoo!!!', 'public') #=>
422
+ # [{:aws_image_id => "ami-e444444d",
423
+ # :aws_reason => "",
424
+ # :aws_state_code => "0",
425
+ # :aws_owner => "000000000888",
426
+ # :aws_instance_id => "i-123f1234",
427
+ # :aws_reservation_id => "r-aabbccdd",
428
+ # :aws_state => "pending",
429
+ # :dns_name => "",
430
+ # :ssh_key_name => "my_awesome_key",
431
+ # :aws_groups => ["my_awesome_group"],
432
+ # :private_dns_name => "",
433
+ # :aws_instance_type => "m1.small",
434
+ # :aws_launch_time => "2008-1-1T00:00:00.000Z"
435
+ # :aws_ramdisk_id => "ari-8605e0ef"
436
+ # :aws_kernel_id => "aki-9905e0f0",
437
+ # :ami_launch_index => "0",
438
+ # :aws_availability_zone => "us-east-1b"
439
+ # }]
440
+ #
441
+ def run_instances(image_id, min_count, max_count, group_ids, key_name, user_data='',
442
+ addressing_type = nil, instance_type = nil,
443
+ kernel_id = nil, ramdisk_id = nil, availability_zone = nil,
444
+ block_device_mappings = nil)
445
+ launch_instances(image_id, { :min_count => min_count,
446
+ :max_count => max_count,
447
+ :user_data => user_data,
448
+ :group_ids => group_ids,
449
+ :key_name => key_name,
450
+ :instance_type => instance_type,
451
+ :addressing_type => addressing_type,
452
+ :kernel_id => kernel_id,
453
+ :ramdisk_id => ramdisk_id,
454
+ :availability_zone => availability_zone,
455
+ :block_device_mappings => block_device_mappings
456
+ })
457
+ end
458
+
459
+
460
+ # Launch new EC2 instances. Returns a list of launched instances or an exception.
461
+ #
462
+ # +lparams+ keys (default values in parenthesis):
463
+ # :min_count fixnum, (1)
464
+ # :max_count fixnum, (1)
465
+ # :group_ids array or string ([] == 'default')
466
+ # :instance_type string (DEFAULT_INSTACE_TYPE)
467
+ # :addressing_type string (DEFAULT_ADDRESSING_TYPE
468
+ # :key_name string
469
+ # :kernel_id string
470
+ # :ramdisk_id string
471
+ # :availability_zone string
472
+ # :block_device_mappings string
473
+ # :user_data string
474
+ #
475
+ # ec2.launch_instances('ami-e444444d', :group_ids => 'my_awesome_group',
476
+ # :user_data => "Woohoo!!!",
477
+ # :addressing_type => "public",
478
+ # :key_name => "my_awesome_key",
479
+ # :availability_zone => "us-east-1c") #=>
480
+ # [{:aws_image_id => "ami-e444444d",
481
+ # :aws_reason => "",
482
+ # :aws_state_code => "0",
483
+ # :aws_owner => "000000000888",
484
+ # :aws_instance_id => "i-123f1234",
485
+ # :aws_reservation_id => "r-aabbccdd",
486
+ # :aws_state => "pending",
487
+ # :dns_name => "",
488
+ # :ssh_key_name => "my_awesome_key",
489
+ # :aws_groups => ["my_awesome_group"],
490
+ # :private_dns_name => "",
491
+ # :aws_instance_type => "m1.small",
492
+ # :aws_launch_time => "2008-1-1T00:00:00.000Z",
493
+ # :aws_ramdisk_id => "ari-8605e0ef"
494
+ # :aws_kernel_id => "aki-9905e0f0",
495
+ # :ami_launch_index => "0",
496
+ # :aws_availability_zone => "us-east-1c"
497
+ # }]
498
+ #
499
+ def launch_instances(image_id, lparams={})
500
+ @logger.info("Launching instance of image #{image_id} for #{@aws_access_key_id}, " +
501
+ "key: #{lparams[:key_name]}, groups: #{(lparams[:group_ids]).to_a.join(',')}")
502
+ # careful: keyName and securityGroups may be nil
503
+ params = hash_params('SecurityGroup', lparams[:group_ids].to_a)
504
+ params.update( {'ImageId' => image_id,
505
+ 'MinCount' => (lparams[:min_count] || 1).to_s,
506
+ 'MaxCount' => (lparams[:max_count] || 1).to_s,
507
+ 'AddressingType' => lparams[:addressing_type] || DEFAULT_ADDRESSING_TYPE,
508
+ 'InstanceType' => lparams[:instance_type] || DEFAULT_INSTANCE_TYPE })
509
+ # optional params
510
+ params['KeyName'] = lparams[:key_name] unless lparams[:key_name].blank?
511
+ params['KernelId'] = lparams[:kernel_id] unless lparams[:kernel_id].blank?
512
+ params['RamdiskId'] = lparams[:ramdisk_id] unless lparams[:ramdisk_id].blank?
513
+ params['Placement.AvailabilityZone'] = lparams[:availability_zone] unless lparams[:availability_zone].blank?
514
+ params['BlockDeviceMappings'] = lparams[:block_device_mappings] unless lparams[:block_device_mappings].blank?
515
+ unless lparams[:user_data].blank?
516
+ lparams[:user_data].strip!
517
+ # Do not use CGI::escape(encode64(...)) as it is done in Amazons EC2 library.
518
+ # Amazon 169.254.169.254 does not like escaped symbols!
519
+ # And it doesn't like "\n" inside of encoded string! Grrr....
520
+ # Otherwise, some of UserData symbols will be lost...
521
+ params['UserData'] = Base64.encode64(lparams[:user_data]).delete("\n") unless lparams[:user_data].blank?
522
+ end
523
+ link = generate_request("RunInstances", params)
524
+ #debugger
525
+ instances = request_info(link, QEc2DescribeInstancesParser.new(:logger => @logger))
526
+ get_desc_instances(instances)
527
+ rescue Exception
528
+ on_exception
529
+ end
530
+
531
+ # Terminates EC2 instances. Returns a list of termination params or an exception.
532
+ #
533
+ # ec2.terminate_instances(['i-f222222d','i-f222222e']) #=>
534
+ # [{:aws_shutdown_state => "shutting-down",
535
+ # :aws_instance_id => "i-f222222d",
536
+ # :aws_shutdown_state_code => 32,
537
+ # :aws_prev_state => "running",
538
+ # :aws_prev_state_code => 16},
539
+ # {:aws_shutdown_state => "shutting-down",
540
+ # :aws_instance_id => "i-f222222e",
541
+ # :aws_shutdown_state_code => 32,
542
+ # :aws_prev_state => "running",
543
+ # :aws_prev_state_code => 16}]
544
+ #
545
+ def terminate_instances(list=[])
546
+ link = generate_request("TerminateInstances", hash_params('InstanceId',list.to_a))
547
+ request_info(link, QEc2TerminateInstancesParser.new(:logger => @logger))
548
+ rescue Exception
549
+ on_exception
550
+ end
551
+
552
+ # Retreive EC2 instance OS logs. Returns a hash of data or an exception.
553
+ #
554
+ # ec2.get_console_output('i-f222222d') =>
555
+ # {:aws_instance_id => 'i-f222222d',
556
+ # :aws_timestamp => "2007-05-23T14:36:07.000-07:00",
557
+ # :timestamp => Wed May 23 21:36:07 UTC 2007, # Time instance
558
+ # :aws_output => "Linux version 2.6.16-xenU (builder@patchbat.amazonsa) (gcc version 4.0.1 20050727 ..."
559
+ def get_console_output(instance_id)
560
+ link = generate_request("GetConsoleOutput", { 'InstanceId.1' => instance_id })
561
+ request_info(link, QEc2GetConsoleOutputParser.new(:logger => @logger))
562
+ rescue Exception
563
+ on_exception
564
+ end
565
+
566
+ # Reboot an EC2 instance. Returns +true+ or an exception.
567
+ #
568
+ # ec2.reboot_instances(['i-f222222d','i-f222222e']) #=> true
569
+ #
570
+ def reboot_instances(list)
571
+ link = generate_request("RebootInstances", hash_params('InstanceId', list.to_a))
572
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
573
+ rescue Exception
574
+ on_exception
575
+ end
576
+
577
+ #-----------------------------------------------------------------
578
+ # Security groups
579
+ #-----------------------------------------------------------------
580
+
581
+ # Retrieve Security Group information. If +list+ is omitted the returns the whole list of groups.
582
+ #
583
+ # ec2.describe_security_groups #=>
584
+ # [{:aws_group_name => "default-1",
585
+ # :aws_owner => "000000000888",
586
+ # :aws_description => "Default allowing SSH, HTTP, and HTTPS ingress",
587
+ # :aws_perms =>
588
+ # [{:owner => "000000000888", :group => "default"},
589
+ # {:owner => "000000000888", :group => "default-1"},
590
+ # {:to_port => "-1", :protocol => "icmp", :from_port => "-1", :cidr_ips => "0.0.0.0/0"},
591
+ # {:to_port => "22", :protocol => "tcp", :from_port => "22", :cidr_ips => "0.0.0.0/0"},
592
+ # {:to_port => "80", :protocol => "tcp", :from_port => "80", :cidr_ips => "0.0.0.0/0"},
593
+ # {:to_port => "443", :protocol => "tcp", :from_port => "443", :cidr_ips => "0.0.0.0/0"}]},
594
+ # ..., {...}]
595
+ #
596
+ def describe_security_groups(list=[])
597
+ link = generate_request("DescribeSecurityGroups", hash_params('GroupName',list.to_a))
598
+ request_cache_or_info( :describe_security_groups, link, QEc2DescribeSecurityGroupsParser, list.blank?) do |parser|
599
+ result = []
600
+ parser.result.each do |item|
601
+ perms = []
602
+ item.ipPermissions.each do |perm|
603
+ perm.groups.each do |ngroup|
604
+ perms << {:group => ngroup.groupName,
605
+ :owner => ngroup.userId}
606
+ end
607
+ perm.ipRanges.each do |cidr_ip|
608
+ perms << {:from_port => perm.fromPort,
609
+ :to_port => perm.toPort,
610
+ :protocol => perm.ipProtocol,
611
+ :cidr_ips => cidr_ip}
612
+ end
613
+ end
614
+
615
+ # delete duplication
616
+ perms.each_index do |i|
617
+ (0...i).each do |j|
618
+ if perms[i] == perms[j] then perms[i] = nil; break; end
619
+ end
620
+ end
621
+ perms.compact!
622
+
623
+ result << {:aws_owner => item.ownerId,
624
+ :aws_group_name => item.groupName,
625
+ :aws_description => item.groupDescription,
626
+ :aws_perms => perms}
627
+
628
+ end
629
+ result
630
+ end
631
+ rescue Exception
632
+ on_exception
633
+ end
634
+
635
+ # Create new Security Group. Returns +true+ or an exception.
636
+ #
637
+ # ec2.create_security_group('default-1',"Default allowing SSH, HTTP, and HTTPS ingress") #=> true
638
+ #
639
+ def create_security_group(name, description)
640
+ # EC2 doesn't like an empty description...
641
+ description = " " if description.blank?
642
+ link = generate_request("CreateSecurityGroup",
643
+ 'GroupName' => name.to_s,
644
+ 'GroupDescription' => description.to_s)
645
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
646
+ rescue Exception
647
+ on_exception
648
+ end
649
+
650
+ # Remove Security Group. Returns +true+ or an exception.
651
+ #
652
+ # ec2.delete_security_group('default-1') #=> true
653
+ #
654
+ def delete_security_group(name)
655
+ link = generate_request("DeleteSecurityGroup",
656
+ 'GroupName' => name.to_s)
657
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
658
+ rescue Exception
659
+ on_exception
660
+ end
661
+
662
+ # Authorize named ingress for security group. Allows instances that are member of someone
663
+ # else's security group to open connections to instances in my group.
664
+ #
665
+ # ec2.authorize_security_group_named_ingress('my_awesome_group', '7011-0219-8268', 'their_group_name') #=> true
666
+ #
667
+ def authorize_security_group_named_ingress(name, owner, group)
668
+ link = generate_request("AuthorizeSecurityGroupIngress",
669
+ 'GroupName' => name.to_s,
670
+ 'SourceSecurityGroupName' => group.to_s,
671
+ 'SourceSecurityGroupOwnerId' => owner.to_s.gsub(/-/,''))
672
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
673
+ rescue Exception
674
+ on_exception
675
+ end
676
+
677
+ # Revoke named ingress for security group.
678
+ #
679
+ # ec2.revoke_security_group_named_ingress('my_awesome_group', aws_user_id, 'another_group_name') #=> true
680
+ #
681
+ def revoke_security_group_named_ingress(name, owner, group)
682
+ link = generate_request("RevokeSecurityGroupIngress",
683
+ 'GroupName' => name.to_s,
684
+ 'SourceSecurityGroupName' => group.to_s,
685
+ 'SourceSecurityGroupOwnerId' => owner.to_s.gsub(/-/,''))
686
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
687
+ rescue Exception
688
+ on_exception
689
+ end
690
+
691
+ # Add permission to a security group. Returns +true+ or an exception. +protocol+ is one of :'tcp'|'udp'|'icmp'.
692
+ #
693
+ # ec2.authorize_security_group_IP_ingress('my_awesome_group', 80, 82, 'udp', '192.168.1.0/8') #=> true
694
+ # ec2.authorize_security_group_IP_ingress('my_awesome_group', -1, -1, 'icmp') #=> true
695
+ #
696
+ def authorize_security_group_IP_ingress(name, from_port, to_port, protocol='tcp', cidr_ip='0.0.0.0/0')
697
+ link = generate_request("AuthorizeSecurityGroupIngress",
698
+ 'GroupName' => name.to_s,
699
+ 'IpProtocol' => protocol.to_s,
700
+ 'FromPort' => from_port.to_s,
701
+ 'ToPort' => to_port.to_s,
702
+ 'CidrIp' => cidr_ip.to_s)
703
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
704
+ rescue Exception
705
+ on_exception
706
+ end
707
+
708
+ # Remove permission from a security group. Returns +true+ or an exception. +protocol+ is one of :'tcp'|'udp'|'icmp' ('tcp' is default).
709
+ #
710
+ # ec2.revoke_security_group_IP_ingress('my_awesome_group', 80, 82, 'udp', '192.168.1.0/8') #=> true
711
+ #
712
+ def revoke_security_group_IP_ingress(name, from_port, to_port, protocol='tcp', cidr_ip='0.0.0.0/0')
713
+ link = generate_request("RevokeSecurityGroupIngress",
714
+ 'GroupName' => name.to_s,
715
+ 'IpProtocol' => protocol.to_s,
716
+ 'FromPort' => from_port.to_s,
717
+ 'ToPort' => to_port.to_s,
718
+ 'CidrIp' => cidr_ip.to_s)
719
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
720
+ rescue Exception
721
+ on_exception
722
+ end
723
+
724
+ #-----------------------------------------------------------------
725
+ # Keys
726
+ #-----------------------------------------------------------------
727
+
728
+ # Retrieve a list of SSH keys. Returns an array of keys or an exception. Each key is
729
+ # represented as a two-element hash.
730
+ #
731
+ # ec2.describe_key_pairs #=>
732
+ # [{: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"},
733
+ # {: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"},
734
+ # ..., {...} ]
735
+ #
736
+ def describe_key_pairs(list=[])
737
+ link = generate_request("DescribeKeyPairs", hash_params('KeyName',list.to_a))
738
+ request_cache_or_info :describe_key_pairs, link, QEc2DescribeKeyPairParser, list.blank?
739
+ rescue Exception
740
+ on_exception
741
+ end
742
+
743
+ # Create new SSH key. Returns a hash of the key's data or an exception.
744
+ #
745
+ # ec2.create_key_pair('my_awesome_key') #=>
746
+ # {:aws_key_name => "my_awesome_key",
747
+ # :aws_fingerprint => "01:02:03:f4:25:e6:97:e8:9b:02:1a:26:32:4e:58:6b:7a:8c:9f:03",
748
+ # :aws_material => "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAK...Q8MDrCbuQ=\n-----END RSA PRIVATE KEY-----"}
749
+ #
750
+ def create_key_pair(name)
751
+ link = generate_request("CreateKeyPair",
752
+ 'KeyName' => name.to_s)
753
+ request_info(link, QEc2CreateKeyPairParser.new(:logger => @logger))
754
+ rescue Exception
755
+ on_exception
756
+ end
757
+
758
+ # Delete a key pair. Returns +true+ or an exception.
759
+ #
760
+ # ec2.delete_key_pair('my_awesome_key') #=> true
761
+ #
762
+ def delete_key_pair(name)
763
+ link = generate_request("DeleteKeyPair",
764
+ 'KeyName' => name.to_s)
765
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
766
+ rescue Exception
767
+ on_exception
768
+ end
769
+
770
+ #-----------------------------------------------------------------
771
+ # Elastic IPs
772
+ #-----------------------------------------------------------------
773
+
774
+ # Acquire a new elastic IP address for use with your account.
775
+ # Returns allocated IP address or an exception.
776
+ #
777
+ # ec2.allocate_address #=> '75.101.154.140'
778
+ #
779
+ def allocate_address
780
+ link = generate_request("AllocateAddress")
781
+ request_info(link, QEc2AllocateAddressParser.new(:logger => @logger))
782
+ rescue Exception
783
+ on_exception
784
+ end
785
+
786
+ # Associate an elastic IP address with an instance.
787
+ # Returns +true+ or an exception.
788
+ #
789
+ # ec2.associate_address('i-d630cbbf', '75.101.154.140') #=> true
790
+ #
791
+ def associate_address(instance_id, public_ip)
792
+ link = generate_request("AssociateAddress",
793
+ "InstanceId" => instance_id.to_s,
794
+ "PublicIp" => public_ip.to_s)
795
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
796
+ rescue Exception
797
+ on_exception
798
+ end
799
+
800
+ # List elastic IP addresses assigned to your account.
801
+ # Returns an array of 2 keys (:instance_id and :public_ip) hashes:
802
+ #
803
+ # ec2.describe_addresses #=> [{:instance_id=>"i-d630cbbf", :public_ip=>"75.101.154.140"},
804
+ # {:instance_id=>nil, :public_ip=>"75.101.154.141"}]
805
+ #
806
+ # ec2.describe_addresses('75.101.154.140') #=> [{:instance_id=>"i-d630cbbf", :public_ip=>"75.101.154.140"}]
807
+ #
808
+ def describe_addresses(list=[])
809
+ link = generate_request("DescribeAddresses",
810
+ hash_params('PublicIp',list.to_a))
811
+ request_cache_or_info :describe_addresses, link, QEc2DescribeAddressesParser, list.blank?
812
+ rescue Exception
813
+ on_exception
814
+ end
815
+
816
+ # Disassociate the specified elastic IP address from the instance to which it is assigned.
817
+ # Returns +true+ or an exception.
818
+ #
819
+ # ec2.disassociate_address('75.101.154.140') #=> true
820
+ #
821
+ def disassociate_address(public_ip)
822
+ link = generate_request("DisassociateAddress",
823
+ "PublicIp" => public_ip.to_s)
824
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
825
+ rescue Exception
826
+ on_exception
827
+ end
828
+
829
+ # Release an elastic IP address associated with your account.
830
+ # Returns +true+ or an exception.
831
+ #
832
+ # ec2.release_address('75.101.154.140') #=> true
833
+ #
834
+ def release_address(public_ip)
835
+ link = generate_request("ReleaseAddress",
836
+ "PublicIp" => public_ip.to_s)
837
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
838
+ rescue Exception
839
+ on_exception
840
+ end
841
+
842
+ #-----------------------------------------------------------------
843
+ # Availability zones
844
+ #-----------------------------------------------------------------
845
+
846
+ # Describes availability zones that are currently available to the account and their states.
847
+ # Returns an array of 2 keys (:zone_name and :zone_state) hashes:
848
+ #
849
+ # ec2.describe_availability_zones #=> [{:zone_state=>"available", :zone_name=>"us-east-1a"},
850
+ # {:zone_state=>"available", :zone_name=>"us-east-1b"},
851
+ # {:zone_state=>"available", :zone_name=>"us-east-1c"}]
852
+ #
853
+ # ec2.describe_availability_zones('us-east-1c') #=> [{:zone_state=>"available", :zone_name=>"us-east-1c"}]
854
+ #
855
+ def describe_availability_zones(list=[])
856
+ link = generate_request("DescribeAvailabilityZones",
857
+ hash_params('ZoneName',list.to_a))
858
+ request_cache_or_info :describe_availability_zones, link, QEc2DescribeAvailabilityZonesParser, list.blank?
859
+ rescue Exception
860
+ on_exception
861
+ end
862
+
863
+
864
+
865
+ #-----------------------------------------------------------------
866
+ # PARSERS: Boolean Response Parser
867
+ #-----------------------------------------------------------------
868
+
869
+ class RightBoolResponseParser < RightAWSParser #:nodoc:
870
+ def tagend(name)
871
+ @result = @text=='true' ? true : false if name == 'return'
872
+ end
873
+ end
874
+
875
+ #-----------------------------------------------------------------
876
+ # PARSERS: Key Pair
877
+ #-----------------------------------------------------------------
878
+
879
+ class QEc2DescribeKeyPairParser < RightAWSParser #:nodoc:
880
+ def tagstart(name, attributes)
881
+ @item = {} if name == 'item'
882
+ end
883
+ def tagend(name)
884
+ case name
885
+ when 'keyName' then @item[:aws_key_name] = @text
886
+ when 'keyFingerprint' then @item[:aws_fingerprint] = @text
887
+ when 'item' then @result << @item
888
+ end
889
+ end
890
+ def reset
891
+ @result = [];
892
+ end
893
+ end
894
+
895
+ class QEc2CreateKeyPairParser < RightAWSParser #:nodoc:
896
+ def tagstart(name, attributes)
897
+ @result = {} if name == 'CreateKeyPairResponse'
898
+ end
899
+ def tagend(name)
900
+ case name
901
+ when 'keyName' then @result[:aws_key_name] = @text
902
+ when 'keyFingerprint' then @result[:aws_fingerprint] = @text
903
+ when 'keyMaterial' then @result[:aws_material] = @text
904
+ end
905
+ end
906
+ end
907
+
908
+ #-----------------------------------------------------------------
909
+ # PARSERS: Security Groups
910
+ #-----------------------------------------------------------------
911
+
912
+ class QEc2UserIdGroupPairType #:nodoc:
913
+ attr_accessor :userId
914
+ attr_accessor :groupName
915
+ end
916
+
917
+ class QEc2IpPermissionType #:nodoc:
918
+ attr_accessor :ipProtocol
919
+ attr_accessor :fromPort
920
+ attr_accessor :toPort
921
+ attr_accessor :groups
922
+ attr_accessor :ipRanges
923
+ end
924
+
925
+ class QEc2SecurityGroupItemType #:nodoc:
926
+ attr_accessor :groupName
927
+ attr_accessor :groupDescription
928
+ attr_accessor :ownerId
929
+ attr_accessor :ipPermissions
930
+ end
931
+
932
+
933
+ class QEc2DescribeSecurityGroupsParser < RightAWSParser #:nodoc:
934
+ def tagstart(name, attributes)
935
+ case name
936
+ when 'item'
937
+ if @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo'
938
+ @group = QEc2SecurityGroupItemType.new
939
+ @group.ipPermissions = []
940
+ elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions'
941
+ @perm = QEc2IpPermissionType.new
942
+ @perm.ipRanges = []
943
+ @perm.groups = []
944
+ elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/groups'
945
+ @sgroup = QEc2UserIdGroupPairType.new
946
+ end
947
+ end
948
+ end
949
+ def tagend(name)
950
+ case name
951
+ when 'ownerId' then @group.ownerId = @text
952
+ when 'groupDescription' then @group.groupDescription = @text
953
+ when 'groupName'
954
+ if @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item'
955
+ @group.groupName = @text
956
+ elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/groups/item'
957
+ @sgroup.groupName = @text
958
+ end
959
+ when 'ipProtocol' then @perm.ipProtocol = @text
960
+ when 'fromPort' then @perm.fromPort = @text
961
+ when 'toPort' then @perm.toPort = @text
962
+ when 'userId' then @sgroup.userId = @text
963
+ when 'cidrIp' then @perm.ipRanges << @text
964
+ when 'item'
965
+ if @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions/item/groups'
966
+ @perm.groups << @sgroup
967
+ elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo/item/ipPermissions'
968
+ @group.ipPermissions << @perm
969
+ elsif @xmlpath=='DescribeSecurityGroupsResponse/securityGroupInfo'
970
+ @result << @group
971
+ end
972
+ end
973
+ end
974
+ def reset
975
+ @result = []
976
+ end
977
+ end
978
+
979
+ #-----------------------------------------------------------------
980
+ # PARSERS: Images
981
+ #-----------------------------------------------------------------
982
+
983
+ class QEc2DescribeImagesParser < RightAWSParser #:nodoc:
984
+ def tagstart(name, attributes)
985
+ if name == 'item' && @xmlpath[%r{.*/imagesSet$}]
986
+ @image = {}
987
+ end
988
+ end
989
+ def tagend(name)
990
+ case name
991
+ when 'imageId' then @image[:aws_id] = @text
992
+ when 'imageLocation' then @image[:aws_location] = @text
993
+ when 'imageState' then @image[:aws_state] = @text
994
+ when 'imageOwnerId' then @image[:aws_owner] = @text
995
+ when 'isPublic' then @image[:aws_is_public]= @text == 'true' ? true : false
996
+ when 'productCode' then (@image[:aws_product_codes] ||= []) << @text
997
+ when 'architecture' then @image[:aws_architecture] = @text
998
+ when 'imageType' then @image[:aws_image_type] = @text
999
+ when 'kernelId' then @image[:aws_kernel_id] = @text
1000
+ when 'ramdiskId' then @image[:aws_ramdisk_id] = @text
1001
+ when 'item' then @result << @image if @xmlpath[%r{.*/imagesSet$}]
1002
+ end
1003
+ end
1004
+ def reset
1005
+ @result = []
1006
+ end
1007
+ end
1008
+
1009
+ class QEc2RegisterImageParser < RightAWSParser #:nodoc:
1010
+ def tagend(name)
1011
+ @result = @text if name == 'imageId'
1012
+ end
1013
+ end
1014
+
1015
+ #-----------------------------------------------------------------
1016
+ # PARSERS: Image Attribute
1017
+ #-----------------------------------------------------------------
1018
+
1019
+ class QEc2DescribeImageAttributeParser < RightAWSParser #:nodoc:
1020
+ def tagstart(name, attributes)
1021
+ case name
1022
+ when 'launchPermission'
1023
+ @result[:groups] = []
1024
+ @result[:users] = []
1025
+ when 'productCodes'
1026
+ @result[:aws_product_codes] = []
1027
+ end
1028
+ end
1029
+ def tagend(name)
1030
+ # right now only 'launchPermission' is supported by Amazon.
1031
+ # But nobody know what will they xml later as attribute. That is why we
1032
+ # check for 'group' and 'userId' inside of 'launchPermission/item'
1033
+ case name
1034
+ when 'imageId' then @result[:aws_id] = @text
1035
+ when 'group' then @result[:groups] << @text if @xmlpath == 'DescribeImageAttributeResponse/launchPermission/item'
1036
+ when 'userId' then @result[:users] << @text if @xmlpath == 'DescribeImageAttributeResponse/launchPermission/item'
1037
+ when 'productCode' then @result[:aws_product_codes] << @text
1038
+ when 'kernel' then @result[:aws_kernel] = @text
1039
+ when 'ramdisk' then @result[:aws_ramdisk] = @text
1040
+ when 'blockDeviceMapping' then @result[:block_device_mapping] = @text
1041
+ end
1042
+ end
1043
+ def reset
1044
+ @result = {}
1045
+ end
1046
+ end
1047
+
1048
+ #-----------------------------------------------------------------
1049
+ # PARSERS: Instances
1050
+ #-----------------------------------------------------------------
1051
+
1052
+ class QEc2DescribeInstancesParser < RightAWSParser #:nodoc:
1053
+ def tagstart(name, attributes)
1054
+ # DescribeInstances property
1055
+ if (name == 'item' && @xmlpath == 'DescribeInstancesResponse/reservationSet') ||
1056
+ # RunInstances property
1057
+ (name == 'RunInstancesResponse')
1058
+ @reservation = { :aws_groups => [],
1059
+ :instances_set => [] }
1060
+
1061
+ elsif (name == 'item') &&
1062
+ # DescribeInstances property
1063
+ ( @xmlpath=='DescribeInstancesResponse/reservationSet/item/instancesSet' ||
1064
+ # RunInstances property
1065
+ @xmlpath=='RunInstancesResponse/instancesSet' )
1066
+ # the optional params (sometimes are missing and we dont want them to be nil)
1067
+ @instance = { :aws_reason => '',
1068
+ :dns_name => '',
1069
+ :private_dns_name => '',
1070
+ :ami_launch_index => '',
1071
+ :ssh_key_name => '',
1072
+ :aws_state => '',
1073
+ :aws_product_codes => [] }
1074
+ end
1075
+ end
1076
+ def tagend(name)
1077
+ case name
1078
+ # reservation
1079
+ when 'reservationId' then @reservation[:aws_reservation_id] = @text
1080
+ when 'ownerId' then @reservation[:aws_owner] = @text
1081
+ when 'groupId' then @reservation[:aws_groups] << @text
1082
+ # instance
1083
+ when 'instanceId' then @instance[:aws_instance_id] = @text
1084
+ when 'imageId' then @instance[:aws_image_id] = @text
1085
+ when 'dnsName' then @instance[:dns_name] = @text
1086
+ when 'privateDnsName' then @instance[:private_dns_name] = @text
1087
+ when 'reason' then @instance[:aws_reason] = @text
1088
+ when 'keyName' then @instance[:ssh_key_name] = @text
1089
+ when 'amiLaunchIndex' then @instance[:ami_launch_index] = @text
1090
+ when 'code' then @instance[:aws_state_code] = @text
1091
+ when 'name' then @instance[:aws_state] = @text
1092
+ when 'productCode' then @instance[:aws_product_codes] << @text
1093
+ when 'instanceType' then @instance[:aws_instance_type] = @text
1094
+ when 'launchTime' then @instance[:aws_launch_time] = @text
1095
+ when 'kernelId' then @instance[:aws_kernel_id] = @text
1096
+ when 'ramdiskId' then @instance[:aws_ramdisk_id] = @text
1097
+ when 'availabilityZone' then @instance[:aws_availability_zone] = @text
1098
+ when 'item'
1099
+ if @xmlpath == 'DescribeInstancesResponse/reservationSet/item/instancesSet' || # DescribeInstances property
1100
+ @xmlpath == 'RunInstancesResponse/instancesSet' # RunInstances property
1101
+ @reservation[:instances_set] << @instance
1102
+ elsif @xmlpath=='DescribeInstancesResponse/reservationSet' # DescribeInstances property
1103
+ @result << @reservation
1104
+ end
1105
+ when 'RunInstancesResponse' then @result << @reservation # RunInstances property
1106
+ end
1107
+ end
1108
+ def reset
1109
+ @result = []
1110
+ end
1111
+ end
1112
+
1113
+ class QEc2ConfirmProductInstanceParser < RightAWSParser #:nodoc:
1114
+ def tagend(name)
1115
+ @result = @text if name == 'ownerId'
1116
+ end
1117
+ end
1118
+
1119
+ class QEc2TerminateInstancesParser < RightAWSParser #:nodoc:
1120
+ def tagstart(name, attributes)
1121
+ @instance = {} if name == 'item'
1122
+ end
1123
+ def tagend(name)
1124
+ case name
1125
+ when 'instanceId' then @instance[:aws_instance_id] = @text
1126
+ when 'code'
1127
+ if @xmlpath == 'TerminateInstancesResponse/instancesSet/item/shutdownState'
1128
+ @instance[:aws_shutdown_state_code] = @text.to_i
1129
+ else @instance[:aws_prev_state_code] = @text.to_i end
1130
+ when 'name'
1131
+ if @xmlpath == 'TerminateInstancesResponse/instancesSet/item/shutdownState'
1132
+ @instance[:aws_shutdown_state] = @text
1133
+ else @instance[:aws_prev_state] = @text end
1134
+ when 'item' then @result << @instance
1135
+ end
1136
+ end
1137
+ def reset
1138
+ @result = []
1139
+ end
1140
+ end
1141
+
1142
+ #-----------------------------------------------------------------
1143
+ # PARSERS: Console
1144
+ #-----------------------------------------------------------------
1145
+
1146
+ class QEc2GetConsoleOutputParser < RightAWSParser #:nodoc:
1147
+ def tagend(name)
1148
+ case name
1149
+ when 'instanceId' then @result[:aws_instance_id] = @text
1150
+ when 'timestamp' then @result[:aws_timestamp] = @text
1151
+ @result[:timestamp] = (Time.parse(@text)).utc
1152
+ when 'output' then @result[:aws_output] = Base64.decode64(@text)
1153
+ end
1154
+ end
1155
+ def reset
1156
+ @result = {}
1157
+ end
1158
+ end
1159
+
1160
+ #-----------------------------------------------------------------
1161
+ # PARSERS: Fake
1162
+ #-----------------------------------------------------------------
1163
+
1164
+ # Dummy parser - does nothing
1165
+ # Returns the original params back
1166
+ class QEc2DummyParser # :nodoc:
1167
+ attr_accessor :result
1168
+ def parse(response, params={})
1169
+ @result = [response, params]
1170
+ end
1171
+ end
1172
+
1173
+ #-----------------------------------------------------------------
1174
+ # PARSERS: Elastic IPs
1175
+ #-----------------------------------------------------------------
1176
+
1177
+ class QEc2AllocateAddressParser < RightAWSParser #:nodoc:
1178
+ def tagend(name)
1179
+ @result = @text if name == 'publicIp'
1180
+ end
1181
+ end
1182
+
1183
+ class QEc2DescribeAddressesParser < RightAWSParser #:nodoc:
1184
+ def tagstart(name, attributes)
1185
+ @address = {} if name == 'item'
1186
+ end
1187
+ def tagend(name)
1188
+ case name
1189
+ when 'instanceId' then @address[:instance_id] = @text.blank? ? nil : @text
1190
+ when 'publicIp' then @address[:public_ip] = @text
1191
+ when 'item' then @result << @address
1192
+ end
1193
+ end
1194
+ def reset
1195
+ @result = []
1196
+ end
1197
+ end
1198
+
1199
+ #-----------------------------------------------------------------
1200
+ # PARSERS: AvailabilityZones
1201
+ #-----------------------------------------------------------------
1202
+
1203
+ class QEc2DescribeAvailabilityZonesParser < RightAWSParser #:nodoc:
1204
+ def tagstart(name, attributes)
1205
+ @zone = {} if name == 'item'
1206
+ end
1207
+ def tagend(name)
1208
+ case name
1209
+ when 'zoneName' then @zone[:zone_name] = @text
1210
+ when 'zoneState' then @zone[:zone_state] = @text
1211
+ when 'item' then @result << @zone
1212
+ end
1213
+ end
1214
+ def reset
1215
+ @result = []
1216
+ end
1217
+ end
1218
+
1219
+
1220
+
1221
+ end
1222
+
1223
+ end