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