icehouse-right_aws 1.11.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/History.txt +93 -15
  2. data/Manifest.txt +15 -1
  3. data/README.txt +0 -4
  4. data/Rakefile +34 -17
  5. data/lib/acf/right_acf_interface.rb +260 -124
  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 +4 -5
  10. data/lib/as/right_as_interface.rb +59 -51
  11. data/lib/awsbase/benchmark_fix.rb +0 -0
  12. data/lib/awsbase/right_awsbase.rb +351 -104
  13. data/lib/awsbase/support.rb +2 -82
  14. data/lib/awsbase/version.rb +9 -0
  15. data/lib/ec2/right_ec2.rb +97 -246
  16. data/lib/ec2/right_ec2_ebs.rb +88 -68
  17. data/lib/ec2/right_ec2_images.rb +90 -50
  18. data/lib/ec2/right_ec2_instances.rb +118 -89
  19. data/lib/ec2/right_ec2_placement_groups.rb +108 -0
  20. data/lib/ec2/right_ec2_reserved_instances.rb +51 -44
  21. data/lib/ec2/right_ec2_security_groups.rb +396 -0
  22. data/lib/ec2/right_ec2_spot_instances.rb +425 -0
  23. data/lib/ec2/right_ec2_tags.rb +139 -0
  24. data/lib/ec2/right_ec2_vpc.rb +152 -140
  25. data/lib/ec2/right_ec2_windows_mobility.rb +84 -0
  26. data/lib/elb/right_elb_interface.rb +205 -39
  27. data/lib/iam/right_iam_access_keys.rb +71 -0
  28. data/lib/iam/right_iam_groups.rb +195 -0
  29. data/lib/iam/right_iam_interface.rb +341 -0
  30. data/lib/iam/right_iam_mfa_devices.rb +67 -0
  31. data/lib/iam/right_iam_users.rb +251 -0
  32. data/lib/rds/right_rds_interface.rb +591 -205
  33. data/lib/right_aws.rb +16 -12
  34. data/lib/route_53/right_route_53_interface.rb +640 -0
  35. data/lib/s3/right_s3.rb +34 -13
  36. data/lib/s3/right_s3_interface.rb +17 -14
  37. data/lib/sdb/active_sdb.rb +215 -38
  38. data/lib/sdb/right_sdb_interface.rb +93 -12
  39. data/lib/sqs/right_sqs.rb +1 -2
  40. data/lib/sqs/right_sqs_gen2.rb +0 -1
  41. data/lib/sqs/right_sqs_gen2_interface.rb +9 -9
  42. data/lib/sqs/right_sqs_interface.rb +6 -7
  43. data/right_aws.gemspec +91 -0
  44. data/test/README.mdown +39 -0
  45. data/test/acf/test_helper.rb +0 -0
  46. data/test/acf/test_right_acf.rb +10 -18
  47. data/test/awsbase/test_helper.rb +0 -0
  48. data/test/awsbase/test_right_awsbase.rb +0 -1
  49. data/test/ec2/test_helper.rb +0 -0
  50. data/test/ec2/test_right_ec2.rb +0 -1
  51. data/test/elb/test_helper.rb +2 -0
  52. data/test/elb/test_right_elb.rb +43 -0
  53. data/test/http_connection.rb +0 -0
  54. data/test/route_53/fixtures/a_record.xml +18 -0
  55. data/test/route_53/fixtures/alias_record.xml +18 -0
  56. data/test/route_53/test_helper.rb +2 -0
  57. data/test/route_53/test_right_route_53.rb +141 -0
  58. data/test/s3/test_helper.rb +0 -0
  59. data/test/s3/test_right_s3.rb +11 -9
  60. data/test/s3/test_right_s3_stubbed.rb +6 -4
  61. data/test/sdb/test_active_sdb.rb +71 -13
  62. data/test/sdb/test_batch_put_attributes.rb +54 -0
  63. data/test/sdb/test_helper.rb +0 -0
  64. data/test/sdb/test_right_sdb.rb +13 -7
  65. data/test/sqs/test_helper.rb +0 -0
  66. data/test/sqs/test_right_sqs.rb +0 -6
  67. data/test/sqs/test_right_sqs_gen2.rb +22 -34
  68. data/test/test_credentials.rb +0 -0
  69. data/test/ts_right_aws.rb +0 -0
  70. metadata +146 -16
  71. data/VERSION +0 -1
@@ -0,0 +1,396 @@
1
+ #
2
+ # Copyright (c) 2010 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
+ # Security groups
30
+ #-----------------------------------------------------------------
31
+
32
+ # Retrieve Security Groups information.
33
+ #
34
+ # Accepts a list of security groups and/or a set of filters as the last parameter.
35
+ #
36
+ # Filters: description, group-name, ip-permission.cidr, ip-permission.from-port, ip-permission.group-name,
37
+ # ip-permission.protocol, ip-permission.to-port, ip-permission.user-id, owner-id
38
+ #
39
+ # # Amazon cloud:
40
+ # ec2 = Rightscale::Ec2.new(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
41
+ # ec2.describe_security_groups #=>
42
+ # [{:aws_perms=>
43
+ # [{:group=>"default", :owner=>"048291609141"},
44
+ # {:to_port=>"22",
45
+ # :protocol=>"tcp",
46
+ # :from_port=>"22",
47
+ # :cidr_ips=>"0.0.0.0/0"},
48
+ # {:to_port=>"9997",
49
+ # :protocol=>"tcp",
50
+ # :from_port=>"9997",
51
+ # :cidr_ips=>"0.0.0.0/0"}],
52
+ # :aws_group_name=>"photo_us",
53
+ # :aws_description=>"default group",
54
+ # :aws_owner=>"826693181925"}]
55
+ #
56
+ # # Eucalyptus cloud:
57
+ # ec2 = Rightscale::Ec2.new(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, :eucalyptus => true)
58
+ # ec2.describe_security_groups #=>
59
+ # [{:aws_perms=>
60
+ # [{:to_port=>"65535",
61
+ # :group=>"default",
62
+ # :protocol=>"tcp",
63
+ # :owner=>"048291609141",
64
+ # :from_port=>"1"},
65
+ # {:to_port=>"65535",
66
+ # :group=>"default",
67
+ # :protocol=>"udp",
68
+ # :owner=>"048291609141",
69
+ # :from_port=>"1"},
70
+ # {:to_port=>"-1",
71
+ # :group=>"default",
72
+ # :protocol=>"icmp",
73
+ # :owner=>"048291609141",
74
+ # :from_port=>"-1"},
75
+ # {:to_port=>"22",
76
+ # :protocol=>"tcp",
77
+ # :from_port=>"22",
78
+ # :cidr_ip=>"0.0.0.0/0"},
79
+ # {:to_port=>"9997",
80
+ # :protocol=>"tcp",
81
+ # :from_port=>"9997",
82
+ # :cidr_ip=>"0.0.0.0/0"}],
83
+ # :aws_group_name=>"photo_us",
84
+ # :aws_description=>"default group",
85
+ # :aws_owner=>"826693181925"}]
86
+ #
87
+ # ec2.describe_security_groups(:filters => {'ip-permission.from-port' => '22'})
88
+ #
89
+ # P.S. filters: http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/ApiReference-query-DescribeSecurityGroups.html
90
+ #
91
+ def describe_security_groups(*list_and_options)
92
+ describe_resources_with_list_and_options('DescribeSecurityGroups', 'GroupName', QEc2DescribeSecurityGroupsParser, list_and_options) do |parser|
93
+ result = []
94
+ parser.result.each do |item|
95
+ result_item = { :aws_owner => item[:owner_id],
96
+ :aws_group_name => item[:group_name],
97
+ :aws_description => item[:group_description] }
98
+ aws_perms = []
99
+ item[:ip_permissions].each do |permission|
100
+ result_perm = {}
101
+ result_perm[:from_port] = permission[:from_port]
102
+ result_perm[:to_port] = permission[:to_port]
103
+ result_perm[:protocol] = permission[:ip_protocol]
104
+ # IP permissions
105
+ Array(permission[:ip_ranges]).each do |ip_range|
106
+ perm = result_perm.dup
107
+ perm[:cidr_ips] = ip_range
108
+ aws_perms << perm
109
+ end
110
+ # Group permissions
111
+ Array(permission[:groups]).each do |group|
112
+ perm = result_perm.dup
113
+ perm[:group] = group[:group_name]
114
+ perm[:owner] = group[:user_id]
115
+ aws_perms << perm
116
+ end
117
+ end
118
+ result_item[:aws_perms] = aws_perms.uniq
119
+ result << result_item
120
+ end
121
+ result
122
+ end
123
+ end
124
+
125
+ # Create new Security Group. Returns +true+ or an exception.
126
+ #
127
+ # ec2.create_security_group('default-1',"Default allowing SSH, HTTP, and HTTPS ingress") #=> true
128
+ #
129
+ def create_security_group(name, description=nil)
130
+ # EC2 doesn't like an empty description...
131
+ description = "-" if description.right_blank?
132
+ link = generate_request("CreateSecurityGroup",
133
+ 'GroupName' => name.to_s,
134
+ 'GroupDescription' => description.to_s)
135
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
136
+ rescue Exception
137
+ on_exception
138
+ end
139
+
140
+ # Remove Security Group. Returns +true+ or an exception.
141
+ #
142
+ # ec2.delete_security_group('default-1') #=> true
143
+ #
144
+ def delete_security_group(name)
145
+ link = generate_request("DeleteSecurityGroup",
146
+ 'GroupName' => name.to_s)
147
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
148
+ rescue Exception
149
+ on_exception
150
+ end
151
+
152
+ # Edit AWS/Eucaliptus security group permissions.
153
+ #
154
+ # Options:
155
+ # action - :authorize (or :grant) | :revoke (or :remove)
156
+ # group_name - security group name
157
+ # permissions - a combination of options below:
158
+ # :source_group_owner => UserId
159
+ # :source_group => GroupName
160
+ # :from_port => from port
161
+ # :to_port => to port
162
+ # :port => set both :from_port and to_port with the same value
163
+ # :protocol => :tcp | :udp | :icmp
164
+ # :cidr_ip => '0.0.0.0/0'
165
+ #
166
+ # ec2.edit_security_group( :grant,
167
+ # 'kd-sg-test',
168
+ # :source_group => "sketchy",
169
+ # :source_group_owner => "600000000006",
170
+ # :protocol => 'tcp',
171
+ # :port => '80',
172
+ # :cidr_ip => '127.0.0.1/32') #=> true
173
+ #
174
+ # P.S. This method is deprecated for AWS and but still good for Eucaliptus clouds.
175
+ # Use +modify_security_group_ingress+ method for AWS clouds.
176
+ #
177
+ def edit_security_group(action, group_name, params)
178
+ hash = {}
179
+ case action
180
+ when :authorize, :grant then action = "AuthorizeSecurityGroupIngress"
181
+ when :revoke, :remove then action = "RevokeSecurityGroupIngress"
182
+ else raise "Unknown action #{action.inspect}!"
183
+ end
184
+ hash['GroupName'] = group_name
185
+ hash['SourceSecurityGroupName'] = params[:source_group] unless params[:source_group].right_blank?
186
+ hash['SourceSecurityGroupOwnerId'] = params[:source_group_owner].to_s.gsub(/-/,'') unless params[:source_group_owner].right_blank?
187
+ hash['IpProtocol'] = params[:protocol] unless params[:protocol].right_blank?
188
+ unless params[:port].right_blank?
189
+ hash['FromPort'] = params[:port]
190
+ hash['ToPort'] = params[:port]
191
+ end
192
+ hash['FromPort'] = params[:from_port] unless params[:from_port].right_blank?
193
+ hash['ToPort'] = params[:to_port] unless params[:to_port].right_blank?
194
+ hash['CidrIp'] = params[:cidr_ip] unless params[:cidr_ip].right_blank?
195
+ #
196
+ link = generate_request(action, hash)
197
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
198
+ rescue Exception
199
+ on_exception
200
+ end
201
+
202
+ # Modify AWS security group permissions.
203
+ #
204
+ # Options:
205
+ # action - :authorize (or :grant) | :revoke (or :remove)
206
+ # group_name - security group name
207
+ # permissions - a combination of options below:
208
+ # # Ports:
209
+ # :from_port => from port
210
+ # :to_port => to port
211
+ # :port => set both :from_port and to_port with the same value
212
+ # # Protocol
213
+ # :protocol => :tcp | :udp | :icmp
214
+ # # Group(s)
215
+ # :source_group_owner => UserId
216
+ # :source_group => GroupName
217
+ # # or
218
+ # :source_groups => { UserId1 => GroupName1, UserName2 => GroupName2 }
219
+ # :source_groups => [ [ UserId1, GroupName1 ], [ UserName2 => GroupName2 ] ]
220
+ # # CidrIp(s)
221
+ # :cidr_ip => '0.0.0.0/0'
222
+ # :cidr_ips => ['1.1.1.1/1', '2.2.2.2/2']
223
+ #
224
+ # # CidrIP based permissions:
225
+ #
226
+ # ec2.modify_security_group_ingress(:authorize, 'my_cool_group',
227
+ # :cidr_ip => "127.0.0.0/31",
228
+ # :port => 811,
229
+ # :protocol => 'tcp' ) #=> true
230
+ #
231
+ # ec2.modify_security_group_ingress(:revoke, 'my_cool_group',
232
+ # :cidr_ips => ["127.0.0.1/32", "127.0.0.2/32"],
233
+ # :port => 812,
234
+ # :protocol => 'tcp' ) #=> true
235
+ #
236
+ # # Group based permissions:
237
+ #
238
+ # ec2.modify_security_group_ingress(:authorize, 'my_cool_group',
239
+ # :source_group_owner => "586789340000",
240
+ # :source_group => "sketchy-us",
241
+ # :port => 800,
242
+ # :protocol => 'tcp' ) #=> true
243
+ #
244
+ # ec2.modify_security_group_ingress(:authorize, 'my_cool_group',
245
+ # :source_groups => { "586789340000" => "sketchy-us",
246
+ # "635201710000" => "sketchy" },
247
+ # :port => 801,
248
+ # :protocol => 'tcp' ) #=> true
249
+ #
250
+ # ec2.modify_security_group_ingress(:revoke, 'my_cool_group',
251
+ # :source_groups => [[ "586789340000", "sketchy-us" ],
252
+ # [ "586789340000", "default" ]],
253
+ # :port => 809,
254
+ # :protocol => 'tcp' ) #=> true
255
+ #
256
+ # # +Permissions+ can be an array of permission hashes:
257
+ #
258
+ # ec2.modify_security_group_ingress(:authorize, 'my_cool_group',
259
+ # [{ :source_groups => { "586789340000" => "sketchy-us",
260
+ # "635201710000" => "sketchy" },
261
+ # :port => 803,
262
+ # :protocol => 'tcp'},
263
+ # { :cidr_ips => ["127.0.0.1/32", "127.0.0.2/32"],
264
+ # :port => 812,
265
+ # :protocol => 'tcp' }]) #=> true
266
+ #
267
+ def modify_security_group_ingress(action, group_name, permissions)
268
+ hash = {}
269
+ case action
270
+ when :authorize, :grant then action = "AuthorizeSecurityGroupIngress"
271
+ when :revoke, :remove then action = "RevokeSecurityGroupIngress"
272
+ else raise "Unknown action #{action.inspect}!"
273
+ end
274
+ # Group Name
275
+ hash["GroupName"] = group_name
276
+ #
277
+ permissions = [permissions] unless permissions.is_a?(Array)
278
+ permissions.each_with_index do |permission, idx|
279
+ pid = idx+1
280
+ # Protocol
281
+ hash["IpPermissions.#{pid}.IpProtocol"] = permission[:protocol]
282
+ # Port
283
+ unless permission[:port].right_blank?
284
+ hash["IpPermissions.#{pid}.FromPort"] = permission[:port]
285
+ hash["IpPermissions.#{pid}.ToPort"] = permission[:port]
286
+ else
287
+ hash["IpPermissions.#{pid}.FromPort"] = permission[:from_port]
288
+ hash["IpPermissions.#{pid}.ToPort"] = permission[:to_port]
289
+ end
290
+ # Source Group(s)
291
+ # Old way (if it is used):
292
+ # :source_group_owner => UserId, :source_group => GroupName
293
+ if !permission[:source_group].right_blank? && !permission[:source_group_owner].right_blank?
294
+ permission[:source_groups] = { permission[:source_group_owner] => permission[:source_group]}
295
+ end
296
+ # # Fix UserId(s): '0000-0000-0000' => '000000000000'
297
+ # permission[:source_groups] = Array(permission[:source_groups])
298
+ # permission[:source_groups].each do |item|
299
+ # item[0] = item[0].to_s.gsub(/-/,'')
300
+ # end
301
+ # New way:
302
+ # :source_groups => {UserId1 => GroupName1, ... UserIdN => GroupNameN}
303
+ # or (this allows using same UserId multiple times )
304
+ # :source_groups => [[UserId1, GroupName1], ... [UserIdN, GroupNameN]]
305
+ hash.merge!(amazonize_list( ["IpPermissions.#{pid}.Groups.?.UserId",
306
+ "IpPermissions.#{pid}.Groups.?.GroupName"],
307
+ permission[:source_groups] ))
308
+ # CidrIp(s)
309
+ cidr_ips = permission[:cidr_ips] unless permission[:cidr_ips].right_blank?
310
+ cidr_ips ||= permission[:cidr_ip] unless permission[:cidr_ip].right_blank?
311
+ hash.merge!(amazonize_list("IpPermissions.1.IpRanges.?.CidrIp", cidr_ips))
312
+ end
313
+ #
314
+ link = generate_request(action, hash)
315
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
316
+ rescue Exception
317
+ on_exception
318
+ end
319
+
320
+ # Authorize named ingress for security group. Allows instances that are member of someone
321
+ # else's security group to open connections to instances in my group.
322
+ #
323
+ # ec2.authorize_security_group_named_ingress('my_awesome_group', '7011-0219-8268', 'their_group_name') #=> true
324
+ #
325
+ def authorize_security_group_named_ingress(name, owner, group)
326
+ edit_security_group( :authorize, name, :source_group_owner => owner, :source_group => group)
327
+ end
328
+
329
+ # Revoke named ingress for security group.
330
+ #
331
+ # ec2.revoke_security_group_named_ingress('my_awesome_group', aws_user_id, 'another_group_name') #=> true
332
+ #
333
+ def revoke_security_group_named_ingress(name, owner, group)
334
+ edit_security_group( :revoke, name, :source_group_owner => owner, :source_group => group)
335
+ end
336
+
337
+ # Add permission to a security group. Returns +true+ or an exception. +protocol+ is one of :'tcp'|'udp'|'icmp'.
338
+ #
339
+ # ec2.authorize_security_group_IP_ingress('my_awesome_group', 80, 82, 'udp', '192.168.1.0/8') #=> true
340
+ # ec2.authorize_security_group_IP_ingress('my_awesome_group', -1, -1, 'icmp') #=> true
341
+ #
342
+ def authorize_security_group_IP_ingress(name, from_port, to_port, protocol='tcp', cidr_ip='0.0.0.0/0')
343
+ edit_security_group( :authorize, name, :from_port => from_port, :to_port => to_port, :protocol => protocol, :cidr_ip => cidr_ip )
344
+ end
345
+
346
+ # Remove permission from a security group. Returns +true+ or an exception. +protocol+ is one of :'tcp'|'udp'|'icmp' ('tcp' is default).
347
+ #
348
+ # ec2.revoke_security_group_IP_ingress('my_awesome_group', 80, 82, 'udp', '192.168.1.0/8') #=> true
349
+ #
350
+ def revoke_security_group_IP_ingress(name, from_port, to_port, protocol='tcp', cidr_ip='0.0.0.0/0')
351
+ edit_security_group( :revoke, name, :from_port => from_port, :to_port => to_port, :protocol => protocol, :cidr_ip => cidr_ip )
352
+ end
353
+
354
+ #-----------------------------------------------------------------
355
+ # PARSERS: Security Groups
356
+ #-----------------------------------------------------------------
357
+
358
+ class QEc2DescribeSecurityGroupsParser < RightAWSParser #:nodoc:
359
+ def tagstart(name, attributes)
360
+ if name == 'item'
361
+ case
362
+ when @xmlpath[/securityGroupInfo$/] then @item = { :ip_permissions => [] }
363
+ when @xmlpath[/ipPermissions$/] then @ip_permission = { :groups => [], :ip_ranges => [] }
364
+ when @xmlpath[/groups$/] then @group = {}
365
+ end
366
+ end
367
+ end
368
+ def tagend(name)
369
+ case name
370
+ when 'ownerId' then @item[:owner_id] = @text
371
+ when 'groupDescription' then @item[:group_description] = @text
372
+ when 'ipProtocol' then @ip_permission[:ip_protocol] = @text
373
+ when 'fromPort' then @ip_permission[:from_port] = @text
374
+ when 'toPort' then @ip_permission[:to_port] = @text
375
+ when 'cidrIp' then @ip_permission[:ip_ranges] << @text
376
+ when 'userId' then @group[:user_id] = @text
377
+ when 'groupName'
378
+ case
379
+ when @xmlpath[/securityGroupInfo\/item$/] then @item[:group_name] = @text
380
+ when @xmlpath[/groups\/item$/] then @group[:group_name] = @text
381
+ end
382
+ when 'item'
383
+ case
384
+ when @xmlpath[/groups$/] then @ip_permission[:groups] << @group
385
+ when @xmlpath[/ipPermissions$/] then @item[:ip_permissions] << @ip_permission
386
+ when @xmlpath[/securityGroupInfo$/]then @result << @item
387
+ end
388
+ end
389
+ end
390
+ def reset
391
+ @result = []
392
+ end
393
+ end
394
+
395
+ end
396
+ end
@@ -0,0 +1,425 @@
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
+ # Spot Instances
30
+ #-----------------------------------------------------------------
31
+
32
+ # Describe Spot Price history.
33
+ #
34
+ # Options: :start_time, :end_time, instance_types, product_description
35
+ #
36
+ # Filters: instance-type, product-description, spot-price, timestamp
37
+ #
38
+ # ec2.describe_spot_price_history #=>
39
+ # [{:spot_price=>0.054,
40
+ # :timestamp=>"2009-12-07T12:12:58.000Z",
41
+ # :product_description=>"Windows",
42
+ # :instance_type=>"m1.small"},
43
+ # {:spot_price=>0.06,
44
+ # :timestamp=>"2009-12-07T12:18:32.000Z",
45
+ # :product_description=>"Linux/UNIX",
46
+ # :instance_type=>"c1.medium"},
47
+ # {:spot_price=>0.198,
48
+ # :timestamp=>"2009-12-07T12:58:00.000Z",
49
+ # :product_description=>"Windows",
50
+ # :instance_type=>"m1.large"},
51
+ # {:spot_price=>0.028,
52
+ # :timestamp=>"2009-12-07T13:48:50.000Z",
53
+ # :product_description=>"Linux/UNIX",
54
+ # :instance_type=>"m1.small"}, ... ]
55
+ #
56
+ # ec2.describe_spot_price_history(:start_time => 1.day.ago,
57
+ # :end_time => 10.minutes.ago,
58
+ # :instance_types => ["c1.medium", "m1.small"],
59
+ # :product_description => "Linux/UNIX" ) #=>
60
+ # [{:product_description=>"Linux/UNIX",
61
+ # :timestamp=>"2010-02-04T05:44:36.000Z",
62
+ # :spot_price=>0.031,
63
+ # :instance_type=>"m1.small"},
64
+ # {:product_description=>"Linux/UNIX",
65
+ # :timestamp=>"2010-02-04T17:56:25.000Z",
66
+ # :spot_price=>0.058,
67
+ # :instance_type=>"c1.medium"}, ... ]
68
+ #
69
+ # ec2.describe_spot_price_history(:filters => {'spot-price' => '0.2' })
70
+ #
71
+ # ec2.describe_spot_price_history(:instance_types => ["c1.medium"], :filters => {'spot-price' => '0.2' })
72
+ #
73
+ #
74
+ # P.S. filters: http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeSpotPriceHistory.html
75
+ #
76
+ def describe_spot_price_history(options={})
77
+ options = options.dup
78
+ request_hash = {}
79
+ request_hash.merge!(amazonize_list(['Filter.?.Name', 'Filter.?.Value.?'], options[:filters])) unless options[:filters].right_blank?
80
+ request_hash['StartTime'] = AwsUtils::utc_iso8601(options[:start_time]) unless options[:start_time].right_blank?
81
+ request_hash['EndTime'] = AwsUtils::utc_iso8601(options[:end_time]) unless options[:end_time].right_blank?
82
+ request_hash['ProductDescription'] = options[:product_description] unless options[:product_description].right_blank?
83
+ request_hash.merge!(amazonize_list('InstanceType', Array(options[:instance_types]))) unless options[:instance_types].right_blank?
84
+ link = generate_request("DescribeSpotPriceHistory", request_hash)
85
+ request_info(link, QEc2DescribeSpotPriceHistoryParser.new)
86
+ rescue Exception
87
+ on_exception
88
+ end
89
+
90
+ # Describe Spot Instance requests.
91
+ #
92
+ # Accepts a list of requests and/or a set of filters as the last parameter.
93
+ #
94
+ # Filters: availability-zone-group, create-time, fault-code, fault-message, instance-id, launch-group,
95
+ # launch.block-device-mapping.delete-on-termination, launch.block-device-mapping.device-name,
96
+ # launch.block-device-mapping.snapshot-id, launch.group-id, launch.image-id, launch.instance-type,
97
+ # launch.kernel-id, launch.key-name, launch.monitoring-enabled, launch.ramdisk-id, product-description,
98
+ # spot-instance-request-id, spot-price, state, tag-key, tag-value, tag:key, type, valid-from, valid-until
99
+ #
100
+ # ec2.describe_spot_instance_requests #=>
101
+ # [{:type=>"one-time",
102
+ # :create_time=>"2010-03-10T10:30:32.000Z",
103
+ # :instance_type=>"c1.medium",
104
+ # :state=>"cancelled",
105
+ # :groups=>["default"],
106
+ # :product_description=>"Linux/UNIX",
107
+ # :spot_instance_request_id=>"sir-bfa06804",
108
+ # :image_id=>"ami-08f41161",
109
+ # :spot_price=>0.01,
110
+ # :monitoring_enabled=>false},
111
+ # {:type=>"one-time",
112
+ # :create_time=>"2010-03-10T10:33:29.000Z",
113
+ # :instance_type=>"c1.medium",
114
+ # :state=>"open",
115
+ # :groups=>["default", "33"],
116
+ # :product_description=>"Linux/UNIX",
117
+ # :spot_instance_request_id=>"sir-b1713a03",
118
+ # :image_id=>"ami-08f41161",
119
+ # :spot_price=>0.01,
120
+ # :monitoring_enabled=>false,
121
+ # :key_name=>"tim"},
122
+ # {:type=>"one-time",
123
+ # :instance_id=>"i-c516ceae",
124
+ # :create_time=>"2010-03-10T10:43:48.000Z",
125
+ # :instance_type=>"c1.medium",
126
+ # :state=>"active",
127
+ # :groups=>["default", "33"],
128
+ # :product_description=>"Linux/UNIX",
129
+ # :spot_instance_request_id=>"sir-5eb6c604",
130
+ # :image_id=>"ami-08f41161",
131
+ # :spot_price=>0.2,
132
+ # :monitoring_enabled=>false,
133
+ # :key_name=>"tim"}]
134
+ #
135
+ # ec2.describe_spot_instance_requests(:filters => {'type'=>"one-time", 'state'=>"open"})
136
+ #
137
+ # P.S. filters: http://docs.amazonwebservices.com/AWSEC2/latest/APIReference/index.html?ApiReference-query-DescribeSpotInstanceRequests.html
138
+ #
139
+ def describe_spot_instance_requests(*list_and_options)
140
+ describe_resources_with_list_and_options('DescribeSpotInstanceRequests', 'SpotInstanceRequestId', QEc2DescribeSpotInstanceParser, list_and_options)
141
+ end
142
+
143
+ # Create a Spot Instance request.
144
+ #
145
+ # Mandatory params: :image_id, :spot_price, :instance_type
146
+ # Optional params: :valid_from, :valid_until, :instance_count, :type, :launch_group,
147
+ # :availability_zone_group, :key_name, :user_data, :addressing_type, :kernel_id,
148
+ # :ramdisk_id, :subnet_id, :availability_zone, :monitoring_enabled, :groups,
149
+ # :block_device_mappings
150
+ #
151
+ # ec2.request_spot_instances(
152
+ # :image_id => 'ami-08f41161',
153
+ # :spot_price => 0.01,
154
+ # :key_name => 'tim',
155
+ # :instance_count => 2,
156
+ # :groups => ['33','default'],
157
+ # :instance_type => 'c1.medium') #=>
158
+ #
159
+ # [{:product_description=>"Linux/UNIX",
160
+ # :type=>"one-time",
161
+ # :spot_instance_requestId=>"sir-7a893003",
162
+ # :monitoring_enabled=>false,
163
+ # :image_id=>"ami-08f41161",
164
+ # :state=>"open",
165
+ # :spot_price=>0.01,
166
+ # :groups=>["default", "33"],
167
+ # :key_name=>"tim",
168
+ # :create_time=>"2010-03-10T10:33:09.000Z",
169
+ # :instance_type=>"c1.medium"},
170
+ # {:product_description=>"Linux/UNIX",
171
+ # :type=>"one-time",
172
+ # :spot_instance_requestId=>"sir-13dc9a03",
173
+ # :monitoring_enabled=>false,
174
+ # :image_id=>"ami-08f41161",
175
+ # :state=>"open",
176
+ # :spot_price=>0.01,
177
+ # :groups=>["default", "33"],
178
+ # :key_name=>"tim",
179
+ # :create_time=>"2010-03-10T10:33:09.000Z",
180
+ # :instance_type=>"c1.medium"}]
181
+ #
182
+ # ec2.request_spot_instances(
183
+ # :image_id => 'ami-08f41161',
184
+ # :spot_price => 0.01,
185
+ # :instance_type => 'm1.small',
186
+ # :valid_from => 10.minutes.since,
187
+ # :valid_until => 1.hour.since,
188
+ # :instance_count => 1,
189
+ # :key_name => 'tim',
190
+ # :groups => ['33','default'],
191
+ # :availability_zone => 'us-east-1a',
192
+ # :monitoring_enabled => true,
193
+ # :launch_group => 'lg1',
194
+ # :availability_zone_group => 'azg1',
195
+ # :block_device_mappings => [ { :device_name => '/dev/sdk',
196
+ # :ebs_snapshot_id => 'snap-145cbc7d',
197
+ # :ebs_delete_on_termination => true,
198
+ # :ebs_volume_size => 3,
199
+ # :virtual_name => 'ephemeral2'
200
+ # } ] ) #=>
201
+ #
202
+ # [{:monitoring_enabled=>true,
203
+ # :type=>"one-time",
204
+ # :image_id=>"ami-08f41161",
205
+ # :launch_group=>"lg1",
206
+ # :state=>"open",
207
+ # :valid_until=>"2010-02-05T19:13:44.000Z",
208
+ # :create_time=>"2010-02-05T18:13:46.000Z",
209
+ # :availability_zone_group=>"azg1",
210
+ # :spot_price=>0.01,
211
+ # :block_device_mappings=>
212
+ # [{:ebs_delete_on_termination=>true,
213
+ # :ebs_volume_size=>3,
214
+ # :virtual_name=>"ephemeral2",
215
+ # :device_name=>"/dev/sdk",
216
+ # :ebs_snapshot_id=>"snap-145cbc7d"}],
217
+ # :instance_type=>"m1.small",
218
+ # :groups=>["default", "33"],
219
+ # :product_description=>"Linux/UNIX",
220
+ # :key_name=>"tim",
221
+ # :valid_from=>"2010-02-05T18:23:44.000Z",
222
+ # :availability_zone=>"us-east-1a",
223
+ # :spot_instance_request_id=>"sir-32da8a03"}]
224
+ #
225
+ def request_spot_instances(options)
226
+ options = options.dup
227
+ request_hash = { 'SpotPrice' => options[:spot_price],
228
+ 'LaunchSpecification.ImageId' => options[:image_id],
229
+ 'LaunchSpecification.InstanceType' => options[:instance_type]}
230
+ request_hash['ValidFrom'] = AwsUtils::utc_iso8601(options[:valid_from]) unless options[:valid_from].right_blank?
231
+ request_hash['ValidUntil'] = AwsUtils::utc_iso8601(options[:valid_until]) unless options[:valid_until].right_blank?
232
+ request_hash['InstanceCount'] = options[:instance_count] unless options[:instance_count].right_blank?
233
+ request_hash['Type'] = options[:type] unless options[:type].right_blank?
234
+ request_hash['LaunchGroup'] = options[:launch_group] unless options[:launch_group].right_blank?
235
+ request_hash['AvailabilityZoneGroup'] = options[:availability_zone_group] unless options[:availability_zone_group].right_blank?
236
+ request_hash['LaunchSpecification.KeyName'] = options[:key_name] unless options[:key_name].right_blank?
237
+ request_hash['LaunchSpecification.AddressingType'] = options[:addressing_type] unless options[:addressing_type].right_blank?
238
+ request_hash['LaunchSpecification.KernelId'] = options[:kernel_id] unless options[:kernel_id].right_blank?
239
+ request_hash['LaunchSpecification.RamdiskId'] = options[:ramdisk_id] unless options[:ramdisk_id].right_blank?
240
+ request_hash['LaunchSpecification.SubnetId'] = options[:subnet_id] unless options[:subnet_id].right_blank?
241
+ request_hash['LaunchSpecification.Placement.AvailabilityZone'] = options[:availability_zone] unless options[:availability_zone].right_blank?
242
+ request_hash['LaunchSpecification.Monitoring.Enabled'] = options[:monitoring_enabled] unless options[:monitoring_enabled].right_blank?
243
+ request_hash.merge!(amazonize_list('LaunchSpecification.SecurityGroup', options[:groups])) unless options[:groups].right_blank?
244
+ request_hash.merge!(amazonize_block_device_mappings(options[:block_device_mappings], 'LaunchSpecification.BlockDeviceMapping'))
245
+ unless options[:user_data].right_blank?
246
+ # See RightAws::Ec2#run_instances
247
+ options[:user_data].strip!
248
+ request_hash['LaunchSpecification.UserData'] = Base64.encode64(options[:user_data]).delete("\n") unless options[:user_data].right_blank?
249
+ end
250
+ link = generate_request("RequestSpotInstances", request_hash)
251
+ request_info(link, QEc2DescribeSpotInstanceParser.new(:logger => @logger))
252
+ end
253
+
254
+ # Cancel one or more Spot Instance requests.
255
+ #
256
+ # ec2.cancel_spot_instance_requests('sir-60662c03',"sir-d3c96e04", "sir-4fa8d804","sir-6992ce04") #=>
257
+ # [{:state=>"cancelled", :spot_instance_request_id=>"sir-60662c03"},
258
+ # {:state=>"cancelled", :spot_instance_request_id=>"sir-6992ce04"},
259
+ # {:state=>"cancelled", :spot_instance_request_id=>"sir-4fa8d804"},
260
+ # {:state=>"cancelled", :spot_instance_request_id=>"sir-d3c96e04"}]
261
+ #
262
+ def cancel_spot_instance_requests(*spot_instance_request_ids)
263
+ link = generate_request("CancelSpotInstanceRequests", amazonize_list('SpotInstanceRequestId', spot_instance_request_ids.flatten))
264
+ request_info(link, QEc2CancelSpotInstanceParser.new(:logger => @logger))
265
+ end
266
+
267
+ # Create the data feed for Spot Instances
268
+ # (Enables to view Spot Instance usage logs)
269
+ #
270
+ # ec2.create_spot_datafeed_subscription('bucket-for-konstantin-eu', 'splogs/') #=>
271
+ # { :owner_id=>"826693181925",
272
+ # :bucket=>"bucket-for-konstantin-eu",
273
+ # :prefix=>"splogs/",
274
+ # :state=>"Active"}
275
+ #
276
+ def create_spot_datafeed_subscription(bucket, prefix=nil)
277
+ request_hash = { 'Bucket' => bucket }
278
+ request_hash['Prefix'] = prefix unless prefix.right_blank?
279
+ link = generate_request("CreateSpotDatafeedSubscription", request_hash)
280
+ request_info(link, QEc2DescribeSpotDatafeedSubscriptionParser.new(:logger => @logger))
281
+ end
282
+
283
+ # Describe the data feed for Spot Instances.
284
+ #
285
+ # ec2.describe_spot_datafeed_subscription #=>
286
+ # { :owner_id=>"826693181925",
287
+ # :bucket=>"bucket-for-konstantin-eu",
288
+ # :prefix=>"splogs/",
289
+ # :state=>"Active"}
290
+ #
291
+ def describe_spot_datafeed_subscription
292
+ link = generate_request("DescribeSpotDatafeedSubscription")
293
+ request_info(link, QEc2DescribeSpotDatafeedSubscriptionParser.new(:logger => @logger))
294
+ end
295
+
296
+ # Delete the data feed for Spot Instances.
297
+ #
298
+ # ec2.delete_spot_datafeed_subscription #=> true
299
+ #
300
+ def delete_spot_datafeed_subscription()
301
+ link = generate_request("DeleteSpotDatafeedSubscription")
302
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
303
+ end
304
+
305
+ #-----------------------------------------------------------------
306
+ # PARSERS: Spot Instances
307
+ #-----------------------------------------------------------------
308
+
309
+ class QEc2DescribeSpotPriceHistoryParser < RightAWSParser #:nodoc:
310
+ def tagstart(name, attributes)
311
+ @item = {} if name == 'item'
312
+ end
313
+ def tagend(name)
314
+ case name
315
+ when 'instanceType' then @item[:instance_type] = @text
316
+ when 'productDescription' then @item[:product_description] = @text
317
+ when 'spotPrice' then @item[:spot_price] = @text.to_f
318
+ when 'timestamp' then @item[:timestamp] = @text
319
+ when 'item' then @result << @item
320
+ end
321
+ end
322
+ def reset
323
+ @result = []
324
+ end
325
+ end
326
+
327
+ class QEc2DescribeSpotInstanceParser < RightAWSParser #:nodoc:
328
+ def tagstart(name, attributes)
329
+ case full_tag_name
330
+ when %r{spotInstanceRequestSet/item$}
331
+ @item = { :tags => {} }
332
+ when %r{/blockDeviceMapping/item$}
333
+ @item[:block_device_mappings] ||= []
334
+ @block_device_mapping = {}
335
+ when %r{/tagSet/item$}
336
+ @aws_tag = {}
337
+ end
338
+ end
339
+ def tagend(name)
340
+ case name
341
+ when 'spotInstanceRequestId' then @item[:spot_instance_request_id]= @text
342
+ when 'spotPrice' then @item[:spot_price] = @text.to_f
343
+ when 'type' then @item[:type] = @text
344
+ when 'state' then @item[:state] = @text
345
+ when 'code' then @item[:fault_code] = @text
346
+ when 'message' then @item[:fault_message] = @text
347
+ when 'validFrom' then @item[:valid_from] = @text
348
+ when 'validUntil' then @item[:valid_until] = @text
349
+ when 'launchGroup' then @item[:launch_group] = @text
350
+ when 'availabilityZoneGroup' then @item[:availability_zone_group] = @text
351
+ when 'imageId' then @item[:image_id] = @text
352
+ when 'keyName' then @item[:key_name] = @text
353
+ when 'userData' then @item[:userData] = @text
354
+ when 'data' then @item[:data] = @text
355
+ when 'addressingType' then @item[:addressing_type] = @text
356
+ when 'instanceType' then @item[:instance_type] = @text
357
+ when 'availabilityZone' then @item[:availability_zone] = @text
358
+ when 'kernelId' then @item[:kernel_id] = @text
359
+ when 'ramdiskId' then @item[:ramdisk_id] = @text
360
+ when 'subnetId' then @item[:subnet_id] = @text
361
+ when 'instanceId' then @item[:instance_id] = @text
362
+ when 'createTime' then @item[:create_time] = @text
363
+ when 'productDescription' then @item[:product_description] = @text
364
+ when 'groupId' then (@item[:groups] ||= []) << @text
365
+ else
366
+ case full_tag_name
367
+ when %r{monitoring/enabled$}
368
+ @item[:monitoring_enabled] = @text == 'true'
369
+ when %r{/blockDeviceMapping/item} # no trailing $
370
+ case name
371
+ when 'deviceName' then @block_device_mapping[:device_name] = @text
372
+ when 'virtualName' then @block_device_mapping[:virtual_name] = @text
373
+ when 'volumeSize' then @block_device_mapping[:ebs_volume_size] = @text.to_i
374
+ when 'snapshotId' then @block_device_mapping[:ebs_snapshot_id] = @text
375
+ when 'deleteOnTermination' then @block_device_mapping[:ebs_delete_on_termination] = @text == 'true' ? true : false
376
+ when 'item' then @item[:block_device_mappings] << @block_device_mapping
377
+ end
378
+ when %r{/tagSet/item/key$} then @aws_tag[:key] = @text
379
+ when %r{/tagSet/item/value$} then @aws_tag[:value] = @text
380
+ when %r{/tagSet/item$} then @item[:tags][@aws_tag[:key]] = @aws_tag[:value]
381
+ when %r{spotInstanceRequestSet/item$} then @result << @item
382
+ end
383
+ end
384
+ end
385
+ def reset
386
+ @result = []
387
+ end
388
+ end
389
+
390
+ class QEc2CancelSpotInstanceParser < RightAWSParser #:nodoc:
391
+ def tagstart(name, attributes)
392
+ @item = {} if name == 'item'
393
+ end
394
+ def tagend(name)
395
+ case name
396
+ when 'spotInstanceRequestId' then @item[:spot_instance_request_id] = @text
397
+ when 'state' then @item[:state] = @text
398
+ when 'item' then @result << @item
399
+ end
400
+ end
401
+ def reset
402
+ @result = []
403
+ end
404
+ end
405
+
406
+ class QEc2DescribeSpotDatafeedSubscriptionParser < RightAWSParser #:nodoc:
407
+ def tagend(name)
408
+ case name
409
+ when 'ownerId' then @result[:owner_id] = @text
410
+ when 'bucket' then @result[:bucket] = @text
411
+ when 'prefix' then @result[:prefix] = @text
412
+ when 'state' then @result[:state] = @text
413
+ when 'fault' then @result[:fault] = @text
414
+ when 'code' then @result[:code] = @text
415
+ when 'message' then @result[:message] = @text
416
+ end
417
+ end
418
+ def reset
419
+ @result = {}
420
+ end
421
+ end
422
+
423
+ end
424
+
425
+ end