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