hackerdude-aws 2.3.25

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