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