right_aws 1.9.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. data/History.txt +164 -13
  2. data/Manifest.txt +28 -1
  3. data/README.txt +12 -10
  4. data/Rakefile +56 -29
  5. data/lib/acf/right_acf_interface.rb +343 -172
  6. data/lib/acf/right_acf_invalidations.rb +144 -0
  7. data/lib/acf/right_acf_origin_access_identities.rb +230 -0
  8. data/lib/acf/right_acf_streaming_interface.rb +229 -0
  9. data/lib/acw/right_acw_interface.rb +248 -0
  10. data/lib/as/right_as_interface.rb +698 -0
  11. data/lib/awsbase/right_awsbase.rb +755 -115
  12. data/lib/awsbase/support.rb +2 -78
  13. data/lib/awsbase/version.rb +9 -0
  14. data/lib/ec2/right_ec2.rb +274 -1294
  15. data/lib/ec2/right_ec2_ebs.rb +514 -0
  16. data/lib/ec2/right_ec2_images.rb +444 -0
  17. data/lib/ec2/right_ec2_instances.rb +797 -0
  18. data/lib/ec2/right_ec2_monitoring.rb +70 -0
  19. data/lib/ec2/right_ec2_placement_groups.rb +108 -0
  20. data/lib/ec2/right_ec2_reserved_instances.rb +243 -0
  21. data/lib/ec2/right_ec2_security_groups.rb +496 -0
  22. data/lib/ec2/right_ec2_spot_instances.rb +422 -0
  23. data/lib/ec2/right_ec2_tags.rb +139 -0
  24. data/lib/ec2/right_ec2_vpc.rb +598 -0
  25. data/lib/ec2/right_ec2_vpc2.rb +382 -0
  26. data/lib/ec2/right_ec2_windows_mobility.rb +84 -0
  27. data/lib/elb/right_elb_interface.rb +573 -0
  28. data/lib/emr/right_emr_interface.rb +728 -0
  29. data/lib/iam/right_iam_access_keys.rb +71 -0
  30. data/lib/iam/right_iam_groups.rb +195 -0
  31. data/lib/iam/right_iam_interface.rb +341 -0
  32. data/lib/iam/right_iam_mfa_devices.rb +67 -0
  33. data/lib/iam/right_iam_users.rb +251 -0
  34. data/lib/rds/right_rds_interface.rb +1657 -0
  35. data/lib/right_aws.rb +30 -13
  36. data/lib/route_53/right_route_53_interface.rb +641 -0
  37. data/lib/s3/right_s3.rb +108 -41
  38. data/lib/s3/right_s3_interface.rb +349 -118
  39. data/lib/sdb/active_sdb.rb +388 -54
  40. data/lib/sdb/right_sdb_interface.rb +323 -64
  41. data/lib/sns/right_sns_interface.rb +286 -0
  42. data/lib/sqs/right_sqs.rb +1 -2
  43. data/lib/sqs/right_sqs_gen2.rb +73 -17
  44. data/lib/sqs/right_sqs_gen2_interface.rb +146 -73
  45. data/lib/sqs/right_sqs_interface.rb +12 -22
  46. data/right_aws.gemspec +91 -0
  47. data/test/README.mdown +39 -0
  48. data/test/acf/test_right_acf.rb +11 -19
  49. data/test/awsbase/test_helper.rb +2 -0
  50. data/test/awsbase/test_right_awsbase.rb +11 -0
  51. data/test/ec2/test_right_ec2.rb +32 -1
  52. data/test/elb/test_helper.rb +2 -0
  53. data/test/elb/test_right_elb.rb +43 -0
  54. data/test/rds/test_helper.rb +2 -0
  55. data/test/rds/test_right_rds.rb +120 -0
  56. data/test/route_53/fixtures/a_record.xml +18 -0
  57. data/test/route_53/fixtures/alias_record.xml +18 -0
  58. data/test/route_53/test_helper.rb +2 -0
  59. data/test/route_53/test_right_route_53.rb +141 -0
  60. data/test/s3/test_right_s3.rb +176 -42
  61. data/test/s3/test_right_s3_stubbed.rb +6 -4
  62. data/test/sdb/test_active_sdb.rb +120 -19
  63. data/test/sdb/test_batch_put_attributes.rb +54 -0
  64. data/test/sdb/test_right_sdb.rb +71 -16
  65. data/test/sns/test_helper.rb +2 -0
  66. data/test/sns/test_right_sns.rb +153 -0
  67. data/test/sqs/test_right_sqs.rb +0 -6
  68. data/test/sqs/test_right_sqs_gen2.rb +104 -49
  69. data/test/ts_right_aws.rb +1 -0
  70. metadata +181 -22
@@ -0,0 +1,444 @@
1
+ #
2
+ # Copyright (c) 2009 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
+ class Ec2
27
+
28
+ #-----------------------------------------------------------------
29
+ # Images
30
+ #-----------------------------------------------------------------
31
+
32
+ # Describe images helper
33
+ # params:
34
+ # { 'ImageId' => ['id1', ..., 'idN'],
35
+ # 'Owner' => ['self', ..., 'userN'],
36
+ # 'ExecutableBy' => ['self', 'all', ..., 'userN']
37
+ # }
38
+ def ec2_describe_images(params={}, options={}, cache_for=nil) #:nodoc:
39
+ request_hash = {}
40
+ params.each { |list_by, list| request_hash.merge! amazonize_list(list_by, Array(list)) }
41
+ request_hash.merge!(amazonize_list(['Filter.?.Name', 'Filter.?.Value.?'], options[:filters])) unless options[:filters].right_blank?
42
+ link = generate_request("DescribeImages", request_hash)
43
+ request_cache_or_info cache_for, link, QEc2DescribeImagesParser, @@bench, cache_for
44
+ rescue Exception
45
+ on_exception
46
+ end
47
+
48
+ # Retrieve a list of images.
49
+ #
50
+ # Accepts a list of images and/or a set of filters as the last parameter.
51
+ #
52
+ # Filters: architecture, block-device-mapping.delete-on-termination block-device-mapping.device-name,
53
+ # block-device-mapping.snapshot-id, block-device-mapping.volume-size, description, image-id, image-type,
54
+ # is-public, kernel-id, manifest-location, name, owner-alias, owner-id, platform, product-code,
55
+ # ramdisk-id, root-device-name, root-device-type, state, state-reason-code, state-reason-message,
56
+ # tag-key, tag-value, tag:key, virtualization-type
57
+ #
58
+ # ec2.describe_images #=>
59
+ # [{:description=>"EBS backed Fedora core 8 i386",
60
+ # :aws_architecture=>"i386",
61
+ # :aws_id=>"ami-c2a3f5d4",
62
+ # :aws_image_type=>"machine",
63
+ # :root_device_name=>"/dev/sda1",
64
+ # :image_class=>"elastic",
65
+ # :aws_owner=>"937766719418",
66
+ # :aws_location=>"937766719418/EBS backed FC8 i386",
67
+ # :aws_state=>"available",
68
+ # :block_device_mappings=>
69
+ # [{:ebs_snapshot_id=>"snap-829a20eb",
70
+ # :ebs_delete_on_termination=>true,
71
+ # :device_name=>"/dev/sda1"}],
72
+ # :name=>"EBS backed FC8 i386",
73
+ # :aws_is_public=>true}, ... ]
74
+ #
75
+ # ec2.describe_images(:filters => { 'image-type' => 'kernel', 'state' => 'available', 'tag:MyTag' => 'MyValue'})
76
+ #
77
+ # ec2.describe_images("ari-fda54b94", "ami-2ee80247", "aki-00896a69",
78
+ # :filters => { 'image-type' => 'kernel', 'state' => 'available' }) #=>
79
+ # [{:root_device_type=>"instance-store",
80
+ # :aws_id=>"aki-00896a69",
81
+ # :aws_image_type=>"kernel",
82
+ # :aws_location=>
83
+ # "karmic-kernel-zul/ubuntu-kernel-2.6.31-300-ec2-i386-20091002-test-04.manifest.xml",
84
+ # :virtualization_type=>"paravirtual",
85
+ # :aws_state=>"available",
86
+ # :aws_owner=>"099720109477",
87
+ # :tags=>{},
88
+ # :aws_is_public=>true,
89
+ # :aws_architecture=>"i386"}]
90
+ #
91
+ # P.S. filters: http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeImages.html
92
+ #
93
+ def describe_images(*list_and_options)
94
+ list, options = AwsUtils::split_items_and_params(list_and_options)
95
+ cache_for = (list.right_blank? && options[:filters].right_blank?) ? :describe_images : nil
96
+ ec2_describe_images( {'ImageId'=>list}, options, cache_for)
97
+ end
98
+
99
+ # Retrieve a list of images by image owner.
100
+ #
101
+ # Accepts a list of images and/or a set of filters as the last parameter.
102
+ #
103
+ # Filters: architecture, block-device-mapping.delete-on-termination block-device-mapping.device-name,
104
+ # block-device-mapping.snapshot-id, block-device-mapping.volume-size, description, image-id, image-type,
105
+ # is-public, kernel-id, manifest-location, name, owner-alias, owner-id, platform, product-code,
106
+ # ramdisk-id, root-device-name, root-device-type, state, state-reason-code, state-reason-message,
107
+ # tag-key, tag-value, tag:key, virtualization-type
108
+ #
109
+ # ec2.describe_images_by_owner('522821470517')
110
+ # ec2.describe_images_by_owner('self', :filters => { 'block-device-mapping.delete-on-termination' => 'false' })
111
+ #
112
+ # P.S. filters: http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeImages.html
113
+ #
114
+ def describe_images_by_owner(*list_and_options)
115
+ list, options = AwsUtils::split_items_and_params(list_and_options)
116
+ list = ['self'] if list.right_blank?
117
+ cache_for = (list==['self'] && options[:filters].right_blank?) ? :describe_images_by_owner : nil
118
+ ec2_describe_images( {'Owner'=>list}, options, cache_for)
119
+ end
120
+
121
+ # Retrieve a list of images by image executable by.
122
+ #
123
+ # Accepts a list of images and/or a set of filters as the last parameter.
124
+ #
125
+ # Filters: architecture, block-device-mapping.delete-on-termination block-device-mapping.device-name,
126
+ # block-device-mapping.snapshot-id, block-device-mapping.volume-size, description, image-id, image-type,
127
+ # is-public, kernel-id, manifest-location, name, owner-alias, owner-id, platform, product-code,
128
+ # ramdisk-id, root-device-name, root-device-type, state, state-reason-code, state-reason-message,
129
+ # tag-key, tag-value, tag:key, virtualization-type
130
+ #
131
+ # ec2.describe_images_by_executable_by('522821470517')
132
+ # ec2.describe_images_by_executable_by('self')
133
+ # ec2.describe_images_by_executable_by('all', :filters => { 'architecture' => 'i386' })
134
+ #
135
+ # P.S. filters: http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeImages.html
136
+ #
137
+ def describe_images_by_executable_by(*list_and_options)
138
+ list, options = AwsUtils::split_items_and_params(list_and_options)
139
+ list = ['self'] if list.right_blank?
140
+ cache_for = (list==['self'] && options[:filters].right_blank?) ? :describe_images_by_executable_by : nil
141
+ ec2_describe_images( {'ExecutableBy'=>list}, options, cache_for)
142
+ end
143
+
144
+ # Register new image at Amazon.
145
+ # Options: :image_location, :name, :description, :architecture, :kernel_id, :ramdisk_id,
146
+ # :root_device_name, :block_device_mappings, :virtualizationt_type(hvm|paravirtual)
147
+ #
148
+ # Returns new image id.
149
+ #
150
+ # # Register S3 image
151
+ # ec2.register_image('bucket_for_k_dzreyev/image_bundles/kd__CentOS_1_10_2009_10_21_13_30_43_MSD/image.manifest.xml') #=> 'ami-e444444d'
152
+ #
153
+ # # or
154
+ # image_reg_params = { :image_location => 'bucket_for_k_dzreyev/image_bundles/kd__CentOS_1_10_2009_10_21_13_30_43_MSD/image.manifest.xml',
155
+ # :name => 'my-test-one-1',
156
+ # :description => 'My first test image' }
157
+ # ec2.register_image(image_reg_params) #=> "ami-bca1f7aa"
158
+ #
159
+ # # Register EBS image
160
+ # image_reg_params = { :name => 'my-test-image',
161
+ # :description => 'My first test image',
162
+ # :root_device_name => "/dev/sda1",
163
+ # :block_device_mappings => [ { :ebs_snapshot_id=>"snap-7360871a",
164
+ # :ebs_delete_on_termination=>true,
165
+ # :device_name=>"/dev/sda1"},
166
+ # { :virtual_name => 'ephemeral0',}
167
+ # :device_name=>"/dev/sdb"} ]
168
+ # ec2.register_image(image_reg_params) #=> "ami-b2a1f7a4"
169
+ #
170
+ def register_image(options)
171
+ case
172
+ when options.is_a?(String)
173
+ options = { :image_location => options }
174
+ when !options.is_a?(Hash)
175
+ raise "Unsupported options type"
176
+ end
177
+ params = {}
178
+ params['ImageLocation'] = options[:image_location] if options[:image_location]
179
+ params['Name'] = options[:name] if options[:name]
180
+ params['Description'] = options[:description] if options[:description]
181
+ params['Architecture'] = options[:architecture] if options[:architecture]
182
+ params['KernelId'] = options[:kernel_id] if options[:kernel_id]
183
+ params['RamdiskId'] = options[:ramdisk_id] if options[:ramdisk_id]
184
+ params['RootDeviceName'] = options[:root_device_name] if options[:root_device_name]
185
+ params['VirtualizationType'] = options[:virtualization_type] if options[:virtualization_type]
186
+ # params['SnapshotId'] = options[:snapshot_id] if options[:snapshot_id]
187
+ params.merge!(amazonize_block_device_mappings(options[:block_device_mappings]))
188
+ link = generate_request("RegisterImage", params)
189
+ request_info(link, QEc2RegisterImageParser.new(:logger => @logger))
190
+ rescue Exception
191
+ on_exception
192
+ end
193
+
194
+ # Deregister image at Amazon. Returns +true+ or an exception.
195
+ #
196
+ # ec2.deregister_image('ami-e444444d') #=> true
197
+ #
198
+ def deregister_image(image_id)
199
+ link = generate_request("DeregisterImage",
200
+ 'ImageId' => image_id.to_s)
201
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
202
+ rescue Exception
203
+ on_exception
204
+ end
205
+
206
+ # Describe image attributes.
207
+ #
208
+ # Returns: String (or nil) for 'description', 'kernel', 'ramdisk'; Hash for 'launchPermission'; Array for 'productCodes', 'blockDeviceMapping'
209
+ #
210
+ # ec2.describe_image_attribute('ami-00000000', 'description') #=> 'My cool Image'
211
+ # ec2.describe_image_attribute('ami-00000000', 'launchPermission') #=> {:user_ids=>["443739700000", "115864000000", "309179000000", "857501300000"]}
212
+ # ec2.describe_image_attribute('ami-00000000', 'productCodes') #=> ["8ED10000"]
213
+ # ec2.describe_image_attribute('ami-00000000', 'kernel') #=> "aki-9b00e5f2"
214
+ # ec2.describe_image_attribute('ami-00000000', 'ramdisk') #=> nil
215
+ # ec2.describe_image_attribute('ami-00000000', 'blockDeviceMapping') #=> [{:device_name=>"sda2", :virtual_name=>"ephemeral0"},
216
+ # {:device_name=>"sda1", :virtual_name=>"ami"},
217
+ # {:device_name=>"/dev/sda1", :virtual_name=>"root"},
218
+ # {:device_name=>"sda3", :virtual_name=>"swap"}]
219
+ #
220
+ def describe_image_attribute(image_id, attribute='launchPermission')
221
+ link = generate_request("DescribeImageAttribute",
222
+ 'ImageId' => image_id,
223
+ 'Attribute' => attribute)
224
+ request_info(link, QEc2DescribeImageAttributeParser.new(:logger => @logger))
225
+ rescue Exception
226
+ on_exception
227
+ end
228
+
229
+ # Reset image attribute. Currently, only 'launchPermission' is supported. Returns +true+ or an exception.
230
+ #
231
+ # ec2.reset_image_attribute('ami-e444444d') #=> true
232
+ #
233
+ def reset_image_attribute(image_id, attribute='launchPermission')
234
+ link = generate_request("ResetImageAttribute",
235
+ 'ImageId' => image_id,
236
+ 'Attribute' => attribute)
237
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
238
+ rescue Exception
239
+ on_exception
240
+ end
241
+
242
+ # Modify an image's attributes. It is recommended that you use
243
+ # modify_image_launch_perm_add_users, modify_image_launch_perm_remove_users, etc.
244
+ # instead of modify_image_attribute because the signature of
245
+ # modify_image_attribute may change with EC2 service changes.
246
+ #
247
+ # Attribute can take next values: 'launchPermission', 'productCode', 'description'.
248
+ # Value is a String for'description'. is a String or an Array for 'productCode' and
249
+ # is a Hash {:add_user_ids, :add_groups, :remove_user_ids, :remove_groups } for 'launchPermission'.
250
+ #
251
+ def modify_image_attribute(image_id, attribute, value)
252
+ params = { 'ImageId' => image_id }
253
+ case attribute.to_s
254
+ when 'launchPermission'
255
+ params.update(amazonize_list('LaunchPermission.Add.?.UserId', value[:add_user_ids]))
256
+ params.update(amazonize_list('LaunchPermission.Add.?.Group', value[:add_groups]))
257
+ params.update(amazonize_list('LaunchPermission.Remove.?.UserId', value[:remove_user_ids]))
258
+ params.update(amazonize_list('LaunchPermission.Remove.?.Group', value[:remove_groups]))
259
+ when 'productCode'
260
+ params.update(amazonize_list('ProductCode', value))
261
+ when 'description'
262
+ params['Description.Value'] = value
263
+ end
264
+ link = generate_request("ModifyImageAttribute", params)
265
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
266
+ rescue Exception
267
+ on_exception
268
+ end
269
+
270
+ # Grant image launch permissions to users.
271
+ # Parameter +user_id+ is a list of user AWS account ids.
272
+ # Returns +true+ or an exception.
273
+ #
274
+ # ec2.modify_image_launch_perm_add_users('ami-e444444d',['000000000777','000000000778']) #=> true
275
+ def modify_image_launch_perm_add_users(image_id, *user_ids)
276
+ modify_image_attribute(image_id, 'launchPermission', :add_user_ids => user_ids.flatten)
277
+ end
278
+
279
+ # Revokes image launch permissions for users. +user_id+ is a list of users AWS accounts ids. Returns +true+ or an exception.
280
+ #
281
+ # ec2.modify_image_launch_perm_remove_users('ami-e444444d',['000000000777','000000000778']) #=> true
282
+ #
283
+ def modify_image_launch_perm_remove_users(image_id, *user_ids)
284
+ modify_image_attribute(image_id, 'launchPermission', :remove_user_ids => user_ids.flatten)
285
+ end
286
+
287
+ # Add image launch permissions for users groups (currently only 'all' is supported, which gives public launch permissions).
288
+ # Returns +true+ or an exception.
289
+ #
290
+ # ec2.modify_image_launch_perm_add_groups('ami-e444444d') #=> true
291
+ #
292
+ def modify_image_launch_perm_add_groups(image_id, *groups)
293
+ modify_image_attribute(image_id, 'launchPermission', :add_groups => groups.flatten)
294
+ end
295
+
296
+ # Remove image launch permissions for users groups (currently only 'all' is supported, which gives public launch permissions).
297
+ #
298
+ # ec2.modify_image_launch_perm_remove_groups('ami-e444444d') #=> true
299
+ #
300
+ def modify_image_launch_perm_remove_groups(image_id, *groups)
301
+ modify_image_attribute(image_id, 'launchPermission', :remove_groups => groups.flatten)
302
+ end
303
+
304
+ # Add product code to image
305
+ #
306
+ # ec2.modify_image_product_code('ami-e444444d','0ABCDEF') #=> true
307
+ #
308
+ def modify_image_product_code(image_id, product_codes=[])
309
+ modify_image_attribute(image_id, 'productCodes',product_codes)
310
+ end
311
+
312
+ # Modify image description
313
+ #
314
+ # ec2.modify_image_product_code('ami-e444444d','My cool image') #=> true
315
+ #
316
+ def modify_image_description(image_id, description)
317
+ modify_image_attribute(image_id, 'description', description)
318
+ end
319
+
320
+ # Create a new image.
321
+ # Options: :name, :description, :no_reboot(bool)
322
+ #
323
+ # ec2.create_image(instance, :description => 'KD: test#1',
324
+ # :no_reboot => true,
325
+ # :name => 'kd-1' ) #=> "ami-84a1f792"
326
+ #
327
+ def create_image(instance_aws_id, options={})
328
+ params = { 'InstanceId' => instance_aws_id }
329
+ params['Name'] = options[:name] unless options[:name].right_blank?
330
+ params['Description'] = options[:description] unless options[:description].right_blank?
331
+ params['NoReboot'] = options[:no_reboot].to_s unless options[:no_reboot].nil?
332
+ link = generate_request("CreateImage", params)
333
+ request_info(link, QEc2RegisterImageParser.new(:logger => @logger))
334
+ end
335
+
336
+ #-----------------------------------------------------------------
337
+ # PARSERS: Images
338
+ #-----------------------------------------------------------------
339
+
340
+ class QEc2DescribeImagesParser < RightAWSParser #:nodoc:
341
+ def tagstart(name, attributes)
342
+ case full_tag_name
343
+ when %r{/imagesSet/item$}
344
+ @item = { :tags => {} }
345
+ when %r{/blockDeviceMapping/item$}
346
+ @item[:block_device_mappings] ||= []
347
+ @block_device_mapping = {}
348
+ when %r{/tagSet/item$}
349
+ @aws_tag = {}
350
+ end
351
+ end
352
+ def tagend(name)
353
+ case name
354
+ when 'imageId' then @item[:aws_id] = @text
355
+ when 'imageLocation' then @item[:aws_location] = @text
356
+ when 'imageState' then @item[:aws_state] = @text
357
+ when 'imageOwnerId' then @item[:aws_owner] = @text
358
+ when 'isPublic' then @item[:aws_is_public] = @text == 'true' ? true : false
359
+ when 'productCode' then (@item[:aws_product_codes] ||= []) << @text
360
+ when 'architecture' then @item[:aws_architecture] = @text
361
+ when 'imageType' then @item[:aws_image_type] = @text
362
+ when 'kernelId' then @item[:aws_kernel_id] = @text
363
+ when 'ramdiskId' then @item[:aws_ramdisk_id] = @text
364
+ when 'platform' then @item[:aws_platform] = @text
365
+ when 'imageOwnerAlias' then @item[:image_owner_alias] = @text
366
+ when 'name' then @item[:name] = @text
367
+ when 'description' then @item[:description] = @text
368
+ when 'rootDeviceType' then @item[:root_device_type] = @text
369
+ when 'rootDeviceName' then @item[:root_device_name] = @text
370
+ when 'imageClass' then @item[:image_class] = @text
371
+ when 'virtualizationType' then @item[:virtualization_type] = @text
372
+ when 'hypervisor' then @item [:hypervisor] = @text
373
+ else
374
+ case full_tag_name
375
+ when %r{/stateReason/code$} then @item[:state_reason_code] = @text.to_i
376
+ when %r{/stateReason/message$} then @item[:state_reason_message] = @text
377
+ when %r{/blockDeviceMapping/item} # no trailing $
378
+ case name
379
+ when 'deviceName' then @block_device_mapping[:device_name] = @text
380
+ when 'virtualName' then @block_device_mapping[:virtual_name] = @text
381
+ when 'volumeSize' then @block_device_mapping[:ebs_volume_size] = @text.to_i
382
+ when 'snapshotId' then @block_device_mapping[:ebs_snapshot_id] = @text
383
+ when 'deleteOnTermination' then @block_device_mapping[:ebs_delete_on_termination] = @text == 'true' ? true : false
384
+ when 'item' then @item[:block_device_mappings] << @block_device_mapping
385
+ end
386
+ when %r{/tagSet/item/key$} then @aws_tag[:key] = @text
387
+ when %r{/tagSet/item/value$} then @aws_tag[:value] = @text
388
+ when %r{/tagSet/item$} then @item[:tags][@aws_tag[:key]] = @aws_tag[:value]
389
+ when %r{/imagesSet/item$} then @result << @item
390
+ end
391
+ end
392
+ end
393
+ def reset
394
+ @result = []
395
+ end
396
+ end
397
+
398
+ class QEc2RegisterImageParser < RightAWSParser #:nodoc:
399
+ def tagend(name)
400
+ @result = @text if name == 'imageId'
401
+ end
402
+ end
403
+
404
+ #-----------------------------------------------------------------
405
+ # PARSERS: Image Attribute
406
+ #-----------------------------------------------------------------
407
+
408
+ class QEc2DescribeImageAttributeParser < RightAWSParser #:nodoc:
409
+ def tagstart(name, attributes)
410
+ case full_tag_name
411
+ when %r{launchPermission$} then @result = {}
412
+ when %r{productCodes$} then @result = []
413
+ when %r{blockDeviceMapping$} then @result = []
414
+ when %r{blockDeviceMapping/item$} then @block_device_mapping = {}
415
+ end
416
+ end
417
+ def tagend(name)
418
+ case full_tag_name
419
+ when %r{/kernel/value$} then @result = @text
420
+ when %r{/ramdisk/value$} then @result = @text
421
+ when %r{/description/value$} then @result = @text
422
+ when %r{/productCode$} then @result << @text
423
+ when %r{launchPermission/item/group$} then (@result[:groups] ||=[]) << @text
424
+ when %r{launchPermission/item/userId$} then (@result[:user_ids]||=[]) << @text
425
+ when %r{/blockDeviceMapping/item} # no trailing $
426
+ case name
427
+ when 'deviceName' then @block_device_mapping[:device_name] = @text
428
+ when 'virtualName' then @block_device_mapping[:virtual_name] = @text
429
+ when 'noDevice' then @block_device_mapping[:no_device] = @text
430
+ when 'snapshotId' then @block_device_mapping[:ebs_snapshot_id] = @text
431
+ when 'volumeSize' then @block_device_mapping[:ebs_volume_size] = @text
432
+ when 'deleteOnTermination' then @block_device_mapping[:ebs_delete_on_termination] = @text == 'true' ? true : false
433
+ when 'item' then @result << @block_device_mapping
434
+ end
435
+ end
436
+ end
437
+ def reset
438
+ @result = nil
439
+ end
440
+ end
441
+
442
+ end
443
+
444
+ end
@@ -0,0 +1,797 @@
1
+ #
2
+ # Copyright (c) 2009 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
+ class Ec2
27
+
28
+ INSTANCE_API_VERSION = (API_VERSION > '2012-07-20') ? API_VERSION : '2012-07-20'
29
+
30
+ #-----------------------------------------------------------------
31
+ # Instances
32
+ #-----------------------------------------------------------------
33
+
34
+ def get_desc_instances(instances) # :nodoc:
35
+ result = []
36
+ instances.each do |reservation|
37
+ reservation[:instances_set].each do |instance|
38
+ # Parse and remove timestamp from the reason string. The timestamp is of
39
+ # the request, not when EC2 took action, thus confusing & useless...
40
+ instance[:aws_reason] = instance[:aws_reason].sub(/\(\d[^)]*GMT\) */, '')
41
+ instance[:aws_owner] = reservation[:aws_owner]
42
+ instance[:aws_reservation_id] = reservation[:aws_reservation_id]
43
+ # Security Groups
44
+ instance[:groups] = instance[:groups].right_blank? ? reservation[:aws_groups] : instance[:groups]
45
+ result << instance
46
+ end
47
+ end
48
+ result
49
+ rescue Exception
50
+ on_exception
51
+ end
52
+
53
+ # Retrieve information about EC2 instances.
54
+ #
55
+ # Accepts a list of instances and/or a set of filters as the last parameter.
56
+ #
57
+ # Filters: architecture, availability-zone, block-device-mapping.attach-time, block-device-mapping.delete-on-termination,
58
+ # block-device-mapping.device-name, block-device-mapping.status, block-device-mapping.volume-id, client-token, dns-name,
59
+ # group-id, image-id, instance-id, instance-lifecycle, instance-state-code, instance-state-name, instance-type, ip-address,
60
+ # kernel-id, key-name, launch-index, launch-time, monitoring-state, owner-id, placement-group-name, platform,
61
+ # private-dns-name, private-ip-address, product-code, ramdisk-id, reason, requester-id, reservation-id, root-device-name,
62
+ # root-device-type, spot-instance-request-id, state-reason-code, state-reason-message, subnet-id, tag-key, tag-value,
63
+ # tag:key, virtualization-type, vpc-id,
64
+ #
65
+ #
66
+ # ec2.describe_instances #=>
67
+ # [{:source_dest_check=>true,
68
+ # :subnet_id=>"subnet-da6cf9b3",
69
+ # :aws_kernel_id=>"aki-3932d150",
70
+ # :ami_launch_index=>"0",
71
+ # :tags=>{},
72
+ # :aws_reservation_id=>"r-7cd25c11",
73
+ # :aws_owner=>"826693181925",
74
+ # :state_reason_code=>"Client.UserInitiatedShutdown",
75
+ # :aws_instance_id=>"i-2d898e41",
76
+ # :hypervisor=>"xen",
77
+ # :root_device_name=>"/dev/sda1",
78
+ # :aws_ramdisk_id=>"ari-c515f6ac",
79
+ # :aws_instance_type=>"m1.large",
80
+ # :groups=>[{:group_name=>"2009-07-15-default", :group_id=>"sg-90c5d6fc"}],
81
+ # :block_device_mappings=>
82
+ # [{:device_name=>"/dev/sda1",
83
+ # :ebs_status=>"attached",
84
+ # :ebs_attach_time=>"2011-03-04T18:51:58.000Z",
85
+ # :ebs_delete_on_termination=>true,
86
+ # :ebs_volume_id=>"vol-38f2bd50"}],
87
+ # :state_reason_message=>
88
+ # "Client.UserInitiatedShutdown: User initiated shutdown",
89
+ # :aws_image_id=>"ami-a3638cca",
90
+ # :virtualization_type=>"paravirtual",
91
+ # :aws_launch_time=>"2011-03-04T18:13:59.000Z",
92
+ # :private_dns_name=>"",
93
+ # :aws_product_codes=>[],
94
+ # :aws_availability_zone=>"us-east-1a",
95
+ # :aws_state_code=>80,
96
+ # :architecture=>"x86_64",
97
+ # :dns_name=>"",
98
+ # :client_token=>"1299262447-684266-NNgyH-ouPTI-MzG6h-5AIRk",
99
+ # :root_device_type=>"ebs",
100
+ # :vpc_id=>"vpc-e16cf988",
101
+ # :monitoring_state=>"disabled",
102
+ # :ssh_key_name=>"default",
103
+ # :private_ip_address=>"192.168.0.52",
104
+ # :aws_reason=>"User initiated ",
105
+ # :aws_state=>"stopped"}, ...]
106
+ #
107
+ # ec2.describe_instances("i-8ce84ae6", "i-8ce84ae8", "i-8ce84ae0")
108
+ # ec2.describe_instances(:filters => { 'availability-zone' => 'us-east-1a', 'instance-type' => 'c1.medium' })
109
+ #
110
+ # P.S. filters: http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeInstances.html
111
+ #
112
+ def describe_instances(*list_and_options)
113
+ list_and_options = merge_new_options_into_list_and_options(list_and_options, :options => {:api_version => INSTANCE_API_VERSION})
114
+ describe_resources_with_list_and_options('DescribeInstances', 'InstanceId', QEc2DescribeInstancesParser, list_and_options) do |parser|
115
+ get_desc_instances(parser.result)
116
+ end
117
+ end
118
+
119
+ # Return the product code attached to instance or +nil+ otherwise.
120
+ #
121
+ # ec2.confirm_product_instance('ami-e444444d','12345678') #=> nil
122
+ # ec2.confirm_product_instance('ami-e444444d','00001111') #=> "000000000888"
123
+ #
124
+ def confirm_product_instance(instance, product_code)
125
+ link = generate_request("ConfirmProductInstance", { 'ProductCode' => product_code,
126
+ 'InstanceId' => instance })
127
+ request_info(link, QEc2ConfirmProductInstanceParser.new(:logger => @logger))
128
+ end
129
+
130
+ # Launch new EC2 instances. Returns a list of launched instances or an exception.
131
+ #
132
+ # ec2.run_instances('ami-e444444d',1,1,['2009-07-15-default'],'my_awesome_key', 'Woohoo!!!', 'public') #=>
133
+ # [{:aws_image_id => "ami-e444444d",
134
+ # :aws_reason => "",
135
+ # :aws_state_code => "0",
136
+ # :aws_owner => "000000000888",
137
+ # :aws_instance_id => "i-123f1234",
138
+ # :aws_reservation_id => "r-aabbccdd",
139
+ # :aws_state => "pending",
140
+ # :dns_name => "",
141
+ # :ssh_key_name => "my_awesome_key",
142
+ # :groups => [{:group_name=>"2009-07-15-default", :group_id=>"sg-90c5d6fc"}],
143
+ # :private_dns_name => "",
144
+ # :aws_instance_type => "m1.small",
145
+ # :aws_launch_time => "2008-1-1T00:00:00.000Z"
146
+ # :aws_ramdisk_id => "ari-8605e0ef"
147
+ # :aws_kernel_id => "aki-9905e0f0",
148
+ # :ami_launch_index => "0",
149
+ # :aws_availability_zone => "us-east-1b"
150
+ # }]
151
+ #
152
+ def run_instances(image_id, min_count, max_count, group_names, key_name, user_data='',
153
+ addressing_type = nil, instance_type = nil,
154
+ kernel_id = nil, ramdisk_id = nil, availability_zone = nil,
155
+ monitoring_enabled = nil, subnet_id = nil, disable_api_termination = nil,
156
+ instance_initiated_shutdown_behavior = nil, block_device_mappings = nil,
157
+ placement_group_name = nil, client_token = nil)
158
+ launch_instances(image_id, { :min_count => min_count,
159
+ :max_count => max_count,
160
+ :user_data => user_data,
161
+ :group_names => group_names,
162
+ :key_name => key_name,
163
+ :instance_type => instance_type,
164
+ :kernel_id => kernel_id,
165
+ :ramdisk_id => ramdisk_id,
166
+ :availability_zone => availability_zone,
167
+ :monitoring_enabled => monitoring_enabled,
168
+ :subnet_id => subnet_id,
169
+ :disable_api_termination => disable_api_termination,
170
+ :instance_initiated_shutdown_behavior => instance_initiated_shutdown_behavior,
171
+ :block_device_mappings => block_device_mappings,
172
+ :placement_group_name => placement_group_name,
173
+ :client_token => client_token
174
+ })
175
+ end
176
+
177
+ # Launch new EC2 instances.
178
+ #
179
+ # Options: :image_id, :min_count, max_count, :key_name, :kernel_id, :ramdisk_id,
180
+ # :availability_zone, :monitoring_enabled, :subnet_id, :disable_api_termination, :instance_initiated_shutdown_behavior,
181
+ # :block_device_mappings, :placement_group_name, :license_pool, :group_ids, :group_names, :private_ip_address,
182
+ # :ebs_optimized
183
+ #
184
+ # Returns a list of launched instances or an exception.
185
+ #
186
+ # ec2.launch_instances( "ami-78779511",
187
+ # :min_count => 1,
188
+ # :group_names => ["default", "eugeg223123123"],
189
+ # :user_data => 'Ohoho!',
190
+ # :availability_zone => "us-east-1a",
191
+ # :disable_api_termination => false,
192
+ # :instance_initiated_shutdown_behavior => 'terminate',
193
+ # :block_device_mappings => [ {:ebs_snapshot_id=>"snap-e40fd188",
194
+ # :ebs_delete_on_termination=>true,
195
+ # :device_name => "/dev/sdk",
196
+ # :virtual_name => "mystorage"} ] ) #=>
197
+ # [{:hypervisor=>"xen",
198
+ # :private_dns_name=>"",
199
+ # :client_token=>"1309532374-551037-gcsBj-gEypk-piG06-ODfQm",
200
+ # :monitoring_state=>"disabled",
201
+ # :aws_availability_zone=>"us-east-1a",
202
+ # :root_device_name=>"/dev/sda1",
203
+ # :state_reason_code=>"pending",
204
+ # :dns_name=>"",
205
+ # :tags=>{},
206
+ # :aws_reason=>"",
207
+ # :virtualization_type=>"paravirtual",
208
+ # :state_reason_message=>"pending",
209
+ # :aws_reservation_id=>"r-6fada703",
210
+ # :aws_ramdisk_id=>"ari-a51cf9cc",
211
+ # :ami_launch_index=>"0",
212
+ # :groups=>
213
+ # [{:group_id=>"sg-a0b85dc9", :group_name=>"default"},
214
+ # {:group_id=>"sg-70733019", :group_name=>"eugeg223123123"}],
215
+ # :aws_owner=>"826693181925",
216
+ # :aws_instance_type=>"m1.small",
217
+ # :aws_state=>"pending",
218
+ # :root_device_type=>"ebs",
219
+ # :aws_image_id=>"ami-78779511",
220
+ # :aws_kernel_id=>"aki-a71cf9ce",
221
+ # :aws_launch_time=>"2011-07-01T14:59:35.000Z",
222
+ # :aws_state_code=>0,
223
+ # :aws_instance_id=>"i-4f202621",
224
+ # :aws_product_codes=>[]}]
225
+ #
226
+ def launch_instances(image_id, options={})
227
+ options[:user_data] = options[:user_data].to_s
228
+ params = map_api_keys_and_values( options,
229
+ :key_name, :kernel_id,
230
+ :ramdisk_id, :subnet_id, :instance_initiated_shutdown_behavior,
231
+ :private_ip_address, :additional_info, :license_pool, :ebs_optimized,
232
+ :image_id => { :value => image_id },
233
+ :min_count => { :value => options[:min_count] || 1 },
234
+ :max_count => { :value => options[:max_count] || options[:min_count] || 1 },
235
+ :placement_tenancy => 'Placement.Tenancy',
236
+ :placement_group_name => 'Placement.GroupName',
237
+ :availability_zone => 'Placement.AvailabilityZone',
238
+ :group_names => { :amazonize_list => 'SecurityGroup' },
239
+ :group_ids => { :amazonize_list => 'SecurityGroupId' },
240
+ :block_device_mappings => { :amazonize_bdm => 'BlockDeviceMapping' },
241
+ :instance_type => { :value => options[:instance_type] || DEFAULT_INSTANCE_TYPE },
242
+ :disable_api_termination => { :value => Proc.new{ !options[:disable_api_termination].nil? && options[:disable_api_termination].to_s }},
243
+ :client_token => { :value => !@params[:eucalyptus] && (options[:client_token] || AwsUtils::generate_unique_token)},
244
+ :user_data => { :value => Proc.new { !options[:user_data].empty? && Base64.encode64(options[:user_data]).delete("\n") }},
245
+ :monitoring_enabled => { :name => 'Monitoring.Enabled',
246
+ :value => Proc.new{ options[:monitoring_enabled] && options[:monitoring_enabled].to_s }})
247
+ # Log debug information
248
+ @logger.info("Launching instance of image #{image_id}. Options: #{params.inspect}")
249
+ # Add IOPS support (default behavior) but skip it when an old API version call is requested
250
+ options[:options] ||= {}
251
+ options[:options][:api_version] ||= INSTANCE_API_VERSION
252
+ params.delete("EbsOptimized") if options[:options][:api_version] < INSTANCE_API_VERSION
253
+ #
254
+ link = generate_request("RunInstances", params, options[:options])
255
+ instances = request_info(link, QEc2DescribeInstancesParser.new(:logger => @logger))
256
+ get_desc_instances(instances)
257
+ rescue Exception
258
+ on_exception
259
+ end
260
+
261
+ # Start instances.
262
+ #
263
+ # ec2.start_instances("i-36e84a5e") #=>
264
+ # [{:aws_prev_state_name=>"stopped",
265
+ # :aws_instance_id=>"i-36e84a5e",
266
+ # :aws_current_state_code=>16,
267
+ # :aws_current_state_name=>"running",
268
+ # :aws_prev_state_code=>80}]
269
+ #
270
+ def start_instances(*instance_aws_ids)
271
+ instance_aws_ids = instance_aws_ids.flatten
272
+ link = generate_request("StartInstances", amazonize_list('InstanceId', instance_aws_ids))
273
+ request_info(link, QEc2TerminateInstancesParser.new(:logger => @logger))
274
+ end
275
+
276
+ # Stop instances.
277
+ #
278
+ # Options: :force => true|false
279
+ #
280
+ # ec2.stop_instances("i-36e84a5e") #=>
281
+ # [{:aws_prev_state_code=>16,
282
+ # :aws_prev_state_name=>"running",
283
+ # :aws_instance_id=>"i-36e84a5e",
284
+ # :aws_current_state_code=>64,
285
+ # :aws_current_state_name=>"stopping"}]
286
+ #
287
+ def stop_instances(*instance_aws_ids_and_options)
288
+ list, options = AwsUtils::split_items_and_params(instance_aws_ids_and_options)
289
+ request_hash = {}
290
+ request_hash['Force'] = true if options[:force]
291
+ request_hash.merge!(amazonize_list('InstanceId', list))
292
+ link = generate_request("StopInstances", request_hash)
293
+ request_info(link, QEc2TerminateInstancesParser.new(:logger => @logger))
294
+ end
295
+
296
+ # Terminates EC2 instances. Returns a list of termination params or an exception.
297
+ #
298
+ # ec2.terminate_instances(['i-cceb49a4']) #=>
299
+ # [{:aws_instance_id=>"i-cceb49a4",
300
+ # :aws_current_state_code=>32,
301
+ # :aws_current_state_name=>"shutting-down",
302
+ # :aws_prev_state_code=>16,
303
+ # :aws_prev_state_name=>"running"}]
304
+ #
305
+ def terminate_instances(*instance_aws_ids)
306
+ instance_aws_ids = instance_aws_ids.flatten
307
+ link = generate_request("TerminateInstances", amazonize_list('InstanceId', instance_aws_ids))
308
+ request_info(link, QEc2TerminateInstancesParser.new(:logger => @logger))
309
+ rescue Exception
310
+ on_exception
311
+ end
312
+
313
+ # Retreive EC2 instance OS logs. Returns a hash of data or an exception.
314
+ #
315
+ # ec2.get_console_output('i-f222222d') =>
316
+ # {:aws_instance_id => 'i-f222222d',
317
+ # :aws_timestamp => "2007-05-23T14:36:07.000-07:00",
318
+ # :timestamp => Wed May 23 21:36:07 UTC 2007, # Time instance
319
+ # :aws_output => "Linux version 2.6.16-xenU (builder@patchbat.amazonsa) (gcc version 4.0.1 20050727 ..."
320
+ def get_console_output(instance_id)
321
+ link = generate_request("GetConsoleOutput", { 'InstanceId.1' => instance_id })
322
+ request_info(link, QEc2GetConsoleOutputParser.new(:logger => @logger))
323
+ rescue Exception
324
+ on_exception
325
+ end
326
+
327
+ # Reboot an EC2 instance. Returns +true+ or an exception.
328
+ #
329
+ # ec2.reboot_instances(['i-f222222d','i-f222222e']) #=> true
330
+ #
331
+ def reboot_instances(*instances)
332
+ instances = instances.flatten
333
+ link = generate_request("RebootInstances", amazonize_list('InstanceId', instances))
334
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
335
+ rescue Exception
336
+ on_exception
337
+ end
338
+
339
+ # Describe instance attribute.
340
+ #
341
+ # Attributes: 'instanceType', 'kernel', 'ramdisk', 'userData', 'rootDeviceName', 'disableApiTermination',
342
+ # 'instanceInitiatedShutdownBehavior', 'sourceDestCheck', 'blockDeviceMapping', 'groupSet'
343
+ #
344
+ # ec2.describe_instance_attribute(instance, "blockDeviceMapping") #=>
345
+ # [{:ebs_delete_on_termination=>true,
346
+ # :ebs_volume_id=>"vol-683dc401",
347
+ # :device_name=>"/dev/sda1"}]
348
+ #
349
+ # ec2.describe_instance_attribute(instance, "instanceType") #=> "m1.small"
350
+ #
351
+ # ec2.describe_instance_attribute(instance, "instanceInitiatedShutdownBehavior") #=> "stop"
352
+ #
353
+ def describe_instance_attribute(instance_id, attribute)
354
+ link = generate_request('DescribeInstanceAttribute',
355
+ 'InstanceId' => instance_id,
356
+ 'Attribute' => attribute)
357
+ value = request_info(link, QEc2DescribeInstanceAttributeParser.new(:logger => @logger))
358
+ value = Base64.decode64(value) if attribute == "userData" && !value.right_blank?
359
+ value
360
+ rescue Exception
361
+ on_exception
362
+ end
363
+
364
+ # Describe instance attribute.
365
+ #
366
+ # Attributes: 'kernel', 'ramdisk', 'sourceDestCheck'
367
+ #
368
+ # ec2.reset_instance_attribute(instance, 'kernel') #=> true
369
+ #
370
+ def reset_instance_attribute(instance_id, attribute)
371
+ link = generate_request('ResetInstanceAttribute',
372
+ 'InstanceId' => instance_id,
373
+ 'Attribute' => attribute )
374
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
375
+ rescue Exception
376
+ on_exception
377
+ end
378
+
379
+ # Modify instance attribute.
380
+ #
381
+ # Attributes: 'InstanceType', 'Kernel', 'Ramdisk', 'UserData', 'DisableApiTermination',
382
+ # 'InstanceInitiatedShutdownBehavior', 'SourceDestCheck', 'GroupId'
383
+ #
384
+ # ec2.modify_instance_attribute(instance, 'instanceInitiatedShutdownBehavior", "stop") #=> true
385
+ #
386
+ def modify_instance_attribute(instance_id, attribute, value)
387
+ request_hash = {'InstanceId' => instance_id}
388
+ attribute = attribute.to_s.right_underscore.right_camelize
389
+ case attribute
390
+ when 'UserData' then request_hash["#{attribute}.Value"] = Base64.encode64(value).delete("\n")
391
+ when 'GroupId' then request_hash.merge!(amazonize_list('GroupId', value))
392
+ else request_hash["#{attribute}.Value"] = value
393
+ end
394
+ link = generate_request('ModifyInstanceAttribute', request_hash, :api_version => INSTANCE_API_VERSION)
395
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
396
+ rescue Exception
397
+ on_exception
398
+ end
399
+
400
+ #-----------------------------------------------------------------
401
+ # Instances: Windows addons
402
+ #-----------------------------------------------------------------
403
+
404
+ # Get initial Windows Server setup password from an instance console output.
405
+ #
406
+ # my_awesome_key = ec2.create_key_pair('my_awesome_key') #=>
407
+ # {:aws_key_name => "my_awesome_key",
408
+ # :aws_fingerprint => "01:02:03:f4:25:e6:97:e8:9b:02:1a:26:32:4e:58:6b:7a:8c:9f:03",
409
+ # :aws_material => "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAK...Q8MDrCbuQ=\n-----END RSA PRIVATE KEY-----"}
410
+ #
411
+ # my_awesome_instance = ec2.run_instances('ami-a000000a',1,1,['my_awesome_group'],'my_awesome_key', 'WindowsInstance!!!') #=>
412
+ # [{:aws_image_id => "ami-a000000a",
413
+ # :aws_instance_id => "i-12345678",
414
+ # ...
415
+ # :aws_availability_zone => "us-east-1b"
416
+ # }]
417
+ #
418
+ # # wait until instance enters 'operational' state and get it's initial password
419
+ #
420
+ # puts ec2.get_initial_password(my_awesome_instance[:aws_instance_id], my_awesome_key[:aws_material]) #=> "MhjWcgZuY6"
421
+ #
422
+ def get_initial_password(instance_id, private_key)
423
+ console_output = get_console_output(instance_id)
424
+ crypted_password = console_output[:aws_output][%r{<Password>(.+)</Password>}m] && $1
425
+ unless crypted_password
426
+ raise AwsError.new("Initial password was not found in console output for #{instance_id}")
427
+ else
428
+ OpenSSL::PKey::RSA.new(private_key).private_decrypt(Base64.decode64(crypted_password))
429
+ end
430
+ rescue Exception
431
+ on_exception
432
+ end
433
+
434
+ # Get Initial windows instance password using Amazon API call GetPasswordData.
435
+ #
436
+ # puts ec2.get_initial_password_v2(my_awesome_instance[:aws_instance_id], my_awesome_key[:aws_material]) #=> "MhjWcgZuY6"
437
+ #
438
+ # P.S. To say the truth there is absolutely no any speedup if to compare to the old get_initial_password method... ;(
439
+ #
440
+ def get_initial_password_v2(instance_id, private_key)
441
+ link = generate_request('GetPasswordData',
442
+ 'InstanceId' => instance_id )
443
+ response = request_info(link, QEc2GetPasswordDataParser.new(:logger => @logger))
444
+ if response[:password_data].right_blank?
445
+ raise AwsError.new("Initial password is not yet created for #{instance_id}")
446
+ else
447
+ OpenSSL::PKey::RSA.new(private_key).private_decrypt(Base64.decode64(response[:password_data]))
448
+ end
449
+ rescue Exception
450
+ on_exception
451
+ end
452
+
453
+ # Bundle a Windows image.
454
+ # Internally, it queues the bundling task and shuts down the instance.
455
+ # It then takes a snapshot of the Windows volume bundles it, and uploads it to
456
+ # S3. After bundling completes, Rightaws::Ec2#register_image may be used to
457
+ # register the new Windows AMI for subsequent launches.
458
+ #
459
+ # ec2.bundle_instance('i-e3e24e8a', 'my-awesome-bucket', 'my-win-image-1') #=>
460
+ # [{:aws_update_time => "2008-10-16T13:58:25.000Z",
461
+ # :s3_bucket => "kd-win-1",
462
+ # :s3_prefix => "win2pr",
463
+ # :aws_state => "pending",
464
+ # :aws_id => "bun-26a7424f",
465
+ # :aws_instance_id => "i-878a25ee",
466
+ # :aws_start_time => "2008-10-16T13:58:02.000Z"}]
467
+ #
468
+ def bundle_instance(instance_id, s3_bucket, s3_prefix,
469
+ s3_owner_aws_access_key_id=nil, s3_owner_aws_secret_access_key=nil,
470
+ s3_expires = S3Interface::DEFAULT_EXPIRES_AFTER,
471
+ s3_upload_policy='ec2-bundle-read')
472
+ # S3 access and signatures
473
+ s3_owner_aws_access_key_id ||= @aws_access_key_id
474
+ s3_owner_aws_secret_access_key ||= @aws_secret_access_key
475
+ s3_expires = Time.now.utc + s3_expires if s3_expires.is_a?(Fixnum) && (s3_expires < S3Interface::ONE_YEAR_IN_SECONDS)
476
+ # policy
477
+ policy = { 'expiration' => AwsUtils::utc_iso8601(s3_expires),
478
+ 'conditions' => [ { 'bucket' => s3_bucket },
479
+ { 'acl' => s3_upload_policy },
480
+ [ 'starts-with', '$key', s3_prefix ] ] }.to_json
481
+ policy64 = Base64.encode64(policy).gsub("\n","")
482
+ signed_policy64 = AwsUtils.sign(s3_owner_aws_secret_access_key, policy64)
483
+ # fill request params
484
+ params = { 'InstanceId' => instance_id,
485
+ 'Storage.S3.AWSAccessKeyId' => s3_owner_aws_access_key_id,
486
+ 'Storage.S3.UploadPolicy' => policy64,
487
+ 'Storage.S3.UploadPolicySignature' => signed_policy64,
488
+ 'Storage.S3.Bucket' => s3_bucket,
489
+ 'Storage.S3.Prefix' => s3_prefix,
490
+ }
491
+ link = generate_request("BundleInstance", params)
492
+ request_info(link, QEc2BundleInstanceParser.new)
493
+ rescue Exception
494
+ on_exception
495
+ end
496
+
497
+ # Describe the status of the Windows AMI bundlings.
498
+ #
499
+ # Accepts a list of tasks and/or a set of filters as the last parameter.
500
+ #
501
+ # Filters" bundle-id, error-code, error-message, instance-id, progress, s3-aws-access-key-id, s3-bucket, s3-prefix,
502
+ # start-time, state, update-time
503
+ #
504
+ # ec2.describe_bundle_tasks('bun-4fa74226') #=>
505
+ # [{:s3_bucket => "my-awesome-bucket"
506
+ # :aws_id => "bun-0fa70206",
507
+ # :s3_prefix => "win1pr",
508
+ # :aws_start_time => "2008-10-14T16:27:57.000Z",
509
+ # :aws_update_time => "2008-10-14T16:37:10.000Z",
510
+ # :aws_error_code => "Client.S3Error",
511
+ # :aws_error_message =>
512
+ # "AccessDenied(403)- Invalid according to Policy: Policy Condition failed: [\"eq\", \"$acl\", \"aws-exec-read\"]",
513
+ # :aws_state => "failed",
514
+ # :aws_instance_id => "i-e3e24e8a"}]
515
+ #
516
+ # ec2.describe_bundle_tasks(:filters => { 'state' => 'pending' })
517
+ #
518
+ # P.S. filters: http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeBundleTasks.html
519
+ #
520
+ def describe_bundle_tasks(*list_and_options)
521
+ describe_resources_with_list_and_options('DescribeBundleTasks', 'BundleId', QEc2DescribeBundleTasksParser, list_and_options)
522
+ end
523
+
524
+ # Cancel an in‐progress or pending bundle task by id.
525
+ #
526
+ # ec2.cancel_bundle_task('bun-73a7421a') #=>
527
+ # [{:s3_bucket => "my-awesome-bucket"
528
+ # :aws_id => "bun-0fa70206",
529
+ # :s3_prefix => "win02",
530
+ # :aws_start_time => "2008-10-14T13:00:29.000Z",
531
+ # :aws_error_message => "User has requested bundling operation cancellation",
532
+ # :aws_state => "failed",
533
+ # :aws_update_time => "2008-10-14T13:01:31.000Z",
534
+ # :aws_error_code => "Client.Cancelled",
535
+ # :aws_instance_id => "i-e3e24e8a"}
536
+ #
537
+ def cancel_bundle_task(bundle_id)
538
+ link = generate_request("CancelBundleTask", { 'BundleId' => bundle_id })
539
+ request_info(link, QEc2BundleInstanceParser.new)
540
+ rescue Exception
541
+ on_exception
542
+ end
543
+
544
+ #-----------------------------------------------------------------
545
+ # PARSERS: Instances
546
+ #-----------------------------------------------------------------
547
+
548
+ class QEc2DescribeInstancesParser < RightAWSParser #:nodoc:
549
+ def tagstart(name, attributes)
550
+ case full_tag_name
551
+ when %r{(RunInstancesResponse|DescribeInstancesResponse/reservationSet/item)$}
552
+ @reservation = { :aws_groups => [],
553
+ :instances_set => [] }
554
+ when %r{(/groupSet/item|instancesSet/item/placement)$}
555
+ @group = {}
556
+ when %r{instancesSet/item$}
557
+ # the optional params (sometimes are missing and we dont want them to be nil)
558
+ @item = { :aws_product_codes => [],
559
+ :groups => [],
560
+ :tags => {} }
561
+ when %r{blockDeviceMapping/item$}
562
+ @item[:block_device_mappings] ||= []
563
+ @block_device_mapping = {}
564
+ when %r{/tagSet/item$}
565
+ @aws_tag = {}
566
+ end
567
+ end
568
+ def tagend(name)
569
+ case name
570
+ when 'reservationId' then @reservation[:aws_reservation_id] = @text
571
+ when 'ownerId' then @reservation[:aws_owner] = @text
572
+ when 'instanceId' then @item[:aws_instance_id] = @text
573
+ when 'imageId' then @item[:aws_image_id] = @text
574
+ when 'privateDnsName' then @item[:private_dns_name] = @text
575
+ when 'dnsName' then @item[:dns_name] = @text
576
+ when 'reason' then @item[:aws_reason] = @text
577
+ when 'keyName' then @item[:ssh_key_name] = @text
578
+ when 'amiLaunchIndex' then @item[:ami_launch_index] = @text
579
+ when 'productCode' then @item[:aws_product_codes] << @text
580
+ when 'instanceType' then @item[:aws_instance_type] = @text
581
+ when 'launchTime' then @item[:aws_launch_time] = @text
582
+ when 'availabilityZone' then @item[:aws_availability_zone] = @text
583
+ when 'kernelId' then @item[:aws_kernel_id] = @text
584
+ when 'ramdiskId' then @item[:aws_ramdisk_id] = @text
585
+ when 'platform' then @item[:aws_platform] = @text
586
+ when 'subnetId' then @item[:subnet_id] = @text
587
+ when 'vpcId' then @item[:vpc_id] = @text
588
+ when 'privateIpAddress' then @item[:private_ip_address] = @text
589
+ when 'ipAddress' then @item[:ip_address] = @text
590
+ when 'architecture' then @item[:architecture] = @text
591
+ when 'rootDeviceType' then @item[:root_device_type] = @text
592
+ when 'rootDeviceName' then @item[:root_device_name] = @text
593
+ when 'instanceClass' then @item[:instance_class] = @text
594
+ when 'instanceLifecycle' then @item[:instance_lifecycle] = @text
595
+ when 'spotInstanceRequestId' then @item[:spot_instance_request_id] = @text
596
+ when 'requesterId' then @item[:requester_id] = @text
597
+ when 'virtualizationType' then @item[:virtualization_type] = @text
598
+ when 'clientToken' then @item[:client_token] = @text
599
+ when 'sourceDestCheck' then @item[:source_dest_check] = @text == 'true'
600
+ when 'tenancy' then @item[:placement_tenancy] = @text
601
+ when 'hypervisor' then @item[:hypervisor] = @text
602
+ when 'ebsOptimized' then @item[:ebs_optimized] = @text == 'true'
603
+ else
604
+ case full_tag_name
605
+ # EC2 Groups
606
+ when %r{(RunInstancesResponse|/reservationSet/item)/groupSet/item/groupId$} then @group[:group_id] = @text
607
+ when %r{(RunInstancesResponse|/reservationSet/item)/groupSet/item/groupName$} then @group[:group_name] = @text
608
+ when %r{(RunInstancesResponse|/reservationSet/item)/groupSet/item$} then @reservation[:aws_groups] << @group
609
+ # VPC Groups
610
+ # KD: It seems that these groups are always present when the groups above present for non VPC instances only
611
+ when %r{/instancesSet/item/groupSet/item/groupId$} then @group[:group_id] = @text
612
+ when %r{/instancesSet/item/groupSet/item/groupName$} then @group[:group_name] = @text
613
+ when %r{/instancesSet/item/groupSet/item$} then @item[:groups] << @group
614
+ # Placement Group Name
615
+ when %r{/placement/groupName$} then @group[:placement_group_name]= @text
616
+ # Codes
617
+ when %r{/stateReason/code$} then @item[:state_reason_code] = @text
618
+ when %r{/stateReason/message$} then @item[:state_reason_message] = @text
619
+ when %r{/instanceState/code$} then @item[:aws_state_code] = @text.to_i
620
+ when %r{/instanceState/name$} then @item[:aws_state] = @text
621
+ when %r{/monitoring/state$} then @item[:monitoring_state] = @text
622
+ when %r{/license/pool$} then @item[:license_pool] = @text
623
+ when %r{/blockDeviceMapping/item} # no trailing $
624
+ case name
625
+ when 'deviceName' then @block_device_mapping[:device_name] = @text
626
+ when 'virtualName' then @block_device_mapping[:virtual_name] = @text
627
+ when 'volumeId' then @block_device_mapping[:ebs_volume_id] = @text
628
+ when 'status' then @block_device_mapping[:ebs_status] = @text
629
+ when 'attachTime' then @block_device_mapping[:ebs_attach_time] = @text
630
+ when 'deleteOnTermination' then @block_device_mapping[:ebs_delete_on_termination] = @text == 'true'
631
+ when 'item' then @item[:block_device_mappings] << @block_device_mapping
632
+ end
633
+ when %r{/instancesSet/item$} then @reservation[:instances_set] << @item
634
+ when %r{/tagSet/item/key$} then @aws_tag[:key] = @text
635
+ when %r{/tagSet/item/value$} then @aws_tag[:value] = @text
636
+ when %r{/tagSet/item$} then @item[:tags][@aws_tag[:key]] = @aws_tag[:value]
637
+ when %r{(RunInstancesResponse|DescribeInstancesResponse/reservationSet/item)$} then @result << @reservation
638
+ end
639
+ end
640
+ end
641
+ def reset
642
+ @result = []
643
+ end
644
+ end
645
+
646
+ class QEc2ConfirmProductInstanceParser < RightAWSParser #:nodoc:
647
+ def tagend(name)
648
+ @result = @text if name == 'ownerId'
649
+ end
650
+ end
651
+
652
+ class QEc2TerminateInstancesParser < RightAWSParser #:nodoc:
653
+ def tagstart(name, attributes)
654
+ @instance = {} if name == 'item'
655
+ end
656
+ def tagend(name)
657
+ case full_tag_name
658
+ when %r{/instanceId$} then @instance[:aws_instance_id] = @text
659
+ when %r{/currentState/code$} then @instance[:aws_current_state_code] = @text.to_i
660
+ when %r{/currentState/name$} then @instance[:aws_current_state_name] = @text
661
+ when %r{/previousState/code$} then @instance[:aws_prev_state_code] = @text.to_i
662
+ when %r{/previousState/name$} then @instance[:aws_prev_state_name] = @text
663
+ when %r{/item$} then @result << @instance
664
+ end
665
+ end
666
+ def reset
667
+ @result = []
668
+ end
669
+ end
670
+
671
+ class QEc2DescribeInstanceAttributeParser < RightAWSParser #:nodoc:
672
+ def tagstart(name, attributes)
673
+ case full_tag_name
674
+ when %r{groupSet$} then @result = []
675
+ when %r{groupSet/item$} then @group = {}
676
+ when %r{blockDeviceMapping$} then @result = []
677
+ when %r{blockDeviceMapping/item$} then @block_device_mapping = {}
678
+ end
679
+ end
680
+ def tagend(name)
681
+ case full_tag_name
682
+ when %r{/instanceType/value$} then @result = @text
683
+ when %r{/kernel/value$} then @result = @text
684
+ when %r{/ramdisk/value$} then @result = @text
685
+ when %r{/userData/value$} then @result = @text
686
+ when %r{/rootDeviceName/value$} then @result = @text
687
+ when %r{/disableApiTermination/value} then @result = @text == 'true'
688
+ when %r{/instanceInitiatedShutdownBehavior/value$} then @result = @text
689
+ when %r{/sourceDestCheck/value$} then @result = @text == 'true'
690
+ when %r{/groupSet/item} # no trailing $
691
+ case name
692
+ when 'groupId' then @group[:group_id] = @text
693
+ when 'groupName' then @group[:group_name] = @text
694
+ when 'item' then @result << @group
695
+ end
696
+ when %r{/blockDeviceMapping/item} # no trailing $
697
+ case name
698
+ when 'deviceName' then @block_device_mapping[:device_name] = @text
699
+ when 'virtualName' then @block_device_mapping[:virtual_name] = @text
700
+ when 'noDevice' then @block_device_mapping[:no_device] = @text
701
+ when 'volumeId' then @block_device_mapping[:ebs_volume_id] = @text
702
+ when 'status' then @block_device_mapping[:ebs_status] = @text
703
+ when 'attachTime' then @block_device_mapping[:ebs_attach_time] = @text
704
+ when 'deleteOnTermination' then @block_device_mapping[:ebs_delete_on_termination] = @text == 'true'
705
+ when 'item' then @result << @block_device_mapping
706
+ end
707
+ end
708
+ end
709
+ def reset
710
+ @result = nil
711
+ end
712
+ end
713
+
714
+ #-----------------------------------------------------------------
715
+ # PARSERS: Console
716
+ #-----------------------------------------------------------------
717
+
718
+ class QEc2GetConsoleOutputParser < RightAWSParser #:nodoc:
719
+ def tagend(name)
720
+ case name
721
+ when 'instanceId' then @result[:aws_instance_id] = @text
722
+ when 'timestamp' then @result[:aws_timestamp] = @text
723
+ @result[:timestamp] = (Time.parse(@text)).utc
724
+ when 'output' then @result[:aws_output] = Base64.decode64(@text)
725
+ end
726
+ end
727
+ def reset
728
+ @result = {}
729
+ end
730
+ end
731
+
732
+ #-----------------------------------------------------------------
733
+ # Instances: Windows related part
734
+ #-----------------------------------------------------------------
735
+
736
+ class QEc2DescribeBundleTasksParser < RightAWSParser #:nodoc:
737
+ def tagstart(name, attributes)
738
+ @bundle = {} if name == 'item'
739
+ end
740
+ def tagend(name)
741
+ case name
742
+ # when 'requestId' then @bundle[:request_id] = @text
743
+ when 'instanceId' then @bundle[:aws_instance_id] = @text
744
+ when 'bundleId' then @bundle[:aws_id] = @text
745
+ when 'bucket' then @bundle[:s3_bucket] = @text
746
+ when 'prefix' then @bundle[:s3_prefix] = @text
747
+ when 'startTime' then @bundle[:aws_start_time] = @text
748
+ when 'updateTime' then @bundle[:aws_update_time] = @text
749
+ when 'state' then @bundle[:aws_state] = @text
750
+ when 'progress' then @bundle[:aws_progress] = @text
751
+ when 'code' then @bundle[:aws_error_code] = @text
752
+ when 'message' then @bundle[:aws_error_message] = @text
753
+ when 'item' then @result << @bundle
754
+ end
755
+ end
756
+ def reset
757
+ @result = []
758
+ end
759
+ end
760
+
761
+ class QEc2BundleInstanceParser < RightAWSParser #:nodoc:
762
+ def tagend(name)
763
+ case name
764
+ # when 'requestId' then @result[:request_id] = @text
765
+ when 'instanceId' then @result[:aws_instance_id] = @text
766
+ when 'bundleId' then @result[:aws_id] = @text
767
+ when 'bucket' then @result[:s3_bucket] = @text
768
+ when 'prefix' then @result[:s3_prefix] = @text
769
+ when 'startTime' then @result[:aws_start_time] = @text
770
+ when 'updateTime' then @result[:aws_update_time] = @text
771
+ when 'state' then @result[:aws_state] = @text
772
+ when 'progress' then @result[:aws_progress] = @text
773
+ when 'code' then @result[:aws_error_code] = @text
774
+ when 'message' then @result[:aws_error_message] = @text
775
+ end
776
+ end
777
+ def reset
778
+ @result = {}
779
+ end
780
+ end
781
+
782
+ class QEc2GetPasswordDataParser < RightAWSParser #:nodoc:
783
+ def tagend(name)
784
+ case name
785
+ when 'instanceId' then @result[:aws_instance_id] = @text
786
+ when 'timestamp' then @result[:timestamp] = @text
787
+ when 'passwordData' then @result[:password_data] = @text
788
+ end
789
+ end
790
+ def reset
791
+ @result = {}
792
+ end
793
+ end
794
+
795
+ end
796
+
797
+ end