aws 1.10.1

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