revans_right_aws 2.0.1

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 (52) hide show
  1. data/.gemtest +0 -0
  2. data/History.txt +284 -0
  3. data/Manifest.txt +50 -0
  4. data/README.txt +167 -0
  5. data/Rakefile +110 -0
  6. data/lib/acf/right_acf_interface.rb +485 -0
  7. data/lib/acf/right_acf_origin_access_identities.rb +230 -0
  8. data/lib/acf/right_acf_streaming_interface.rb +236 -0
  9. data/lib/acw/right_acw_interface.rb +249 -0
  10. data/lib/as/right_as_interface.rb +699 -0
  11. data/lib/awsbase/benchmark_fix.rb +39 -0
  12. data/lib/awsbase/right_awsbase.rb +978 -0
  13. data/lib/awsbase/support.rb +115 -0
  14. data/lib/ec2/right_ec2.rb +395 -0
  15. data/lib/ec2/right_ec2_ebs.rb +452 -0
  16. data/lib/ec2/right_ec2_images.rb +373 -0
  17. data/lib/ec2/right_ec2_instances.rb +755 -0
  18. data/lib/ec2/right_ec2_monitoring.rb +70 -0
  19. data/lib/ec2/right_ec2_reserved_instances.rb +170 -0
  20. data/lib/ec2/right_ec2_security_groups.rb +277 -0
  21. data/lib/ec2/right_ec2_spot_instances.rb +399 -0
  22. data/lib/ec2/right_ec2_vpc.rb +571 -0
  23. data/lib/elb/right_elb_interface.rb +496 -0
  24. data/lib/rds/right_rds_interface.rb +998 -0
  25. data/lib/right_aws.rb +83 -0
  26. data/lib/s3/right_s3.rb +1126 -0
  27. data/lib/s3/right_s3_interface.rb +1199 -0
  28. data/lib/sdb/active_sdb.rb +1122 -0
  29. data/lib/sdb/right_sdb_interface.rb +721 -0
  30. data/lib/sqs/right_sqs.rb +388 -0
  31. data/lib/sqs/right_sqs_gen2.rb +343 -0
  32. data/lib/sqs/right_sqs_gen2_interface.rb +524 -0
  33. data/lib/sqs/right_sqs_interface.rb +594 -0
  34. data/test/acf/test_helper.rb +2 -0
  35. data/test/acf/test_right_acf.rb +138 -0
  36. data/test/ec2/test_helper.rb +2 -0
  37. data/test/ec2/test_right_ec2.rb +108 -0
  38. data/test/http_connection.rb +87 -0
  39. data/test/rds/test_helper.rb +2 -0
  40. data/test/rds/test_right_rds.rb +120 -0
  41. data/test/s3/test_helper.rb +2 -0
  42. data/test/s3/test_right_s3.rb +421 -0
  43. data/test/s3/test_right_s3_stubbed.rb +97 -0
  44. data/test/sdb/test_active_sdb.rb +357 -0
  45. data/test/sdb/test_helper.rb +3 -0
  46. data/test/sdb/test_right_sdb.rb +253 -0
  47. data/test/sqs/test_helper.rb +2 -0
  48. data/test/sqs/test_right_sqs.rb +291 -0
  49. data/test/sqs/test_right_sqs_gen2.rb +264 -0
  50. data/test/test_credentials.rb +37 -0
  51. data/test/ts_right_aws.rb +14 -0
  52. metadata +169 -0
@@ -0,0 +1,399 @@
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
+ # Options: :start_time, :end_time, instance_types, product_description
34
+ #
35
+ # ec2.describe_spot_price_history #=>
36
+ # [{:spot_price=>0.054,
37
+ # :timestamp=>"2009-12-07T12:12:58.000Z",
38
+ # :product_description=>"Windows",
39
+ # :instance_type=>"m1.small"},
40
+ # {:spot_price=>0.06,
41
+ # :timestamp=>"2009-12-07T12:18:32.000Z",
42
+ # :product_description=>"Linux/UNIX",
43
+ # :instance_type=>"c1.medium"},
44
+ # {:spot_price=>0.198,
45
+ # :timestamp=>"2009-12-07T12:58:00.000Z",
46
+ # :product_description=>"Windows",
47
+ # :instance_type=>"m1.large"},
48
+ # {:spot_price=>0.028,
49
+ # :timestamp=>"2009-12-07T13:48:50.000Z",
50
+ # :product_description=>"Linux/UNIX",
51
+ # :instance_type=>"m1.small"}, ... ]
52
+ #
53
+ # ec2.describe_spot_price_history(:start_time => 1.day.ago,
54
+ # :end_time => 10.minutes.ago,
55
+ # :instance_types => ["c1.medium", "m1.small"],
56
+ # :product_description => "Linux/UNIX" ) #=>
57
+ # [{:product_description=>"Linux/UNIX",
58
+ # :timestamp=>"2010-02-04T05:44:36.000Z",
59
+ # :spot_price=>0.031,
60
+ # :instance_type=>"m1.small"},
61
+ # {:product_description=>"Linux/UNIX",
62
+ # :timestamp=>"2010-02-04T17:56:25.000Z",
63
+ # :spot_price=>0.058,
64
+ # :instance_type=>"c1.medium"}, ... ]
65
+ #
66
+ def describe_spot_price_history(options={})
67
+ options = options.dup
68
+ request_hash = {}
69
+ request_hash['StartTime'] = AwsUtils::utc_iso8601(options[:start_time]) unless options[:start_time].blank?
70
+ request_hash['EndTime'] = AwsUtils::utc_iso8601(options[:end_time]) unless options[:end_time].blank?
71
+ request_hash['ProductDescription'] = options[:product_description] unless options[:product_description].blank?
72
+ request_hash.merge!(amazonize_list('InstanceType', Array(options[:instance_types]))) unless options[:instance_types].blank?
73
+ link = generate_request("DescribeSpotPriceHistory", request_hash)
74
+ request_info(link, QEc2DescribeSpotPriceHistoryParser.new)
75
+ rescue Exception
76
+ on_exception
77
+ end
78
+
79
+ # Describe Spot Instance requests.
80
+ #
81
+ # ec2.describe_spot_instance_requests #=>
82
+ # [{:type=>"one-time",
83
+ # :create_time=>"2010-03-10T10:30:32.000Z",
84
+ # :instance_type=>"c1.medium",
85
+ # :state=>"cancelled",
86
+ # :groups=>["default"],
87
+ # :product_description=>"Linux/UNIX",
88
+ # :spot_instance_request_id=>"sir-bfa06804",
89
+ # :image_id=>"ami-08f41161",
90
+ # :spot_price=>0.01,
91
+ # :monitoring_enabled=>false},
92
+ # {:type=>"one-time",
93
+ # :create_time=>"2010-03-10T10:33:29.000Z",
94
+ # :instance_type=>"c1.medium",
95
+ # :state=>"open",
96
+ # :groups=>["default", "33"],
97
+ # :product_description=>"Linux/UNIX",
98
+ # :spot_instance_request_id=>"sir-b1713a03",
99
+ # :image_id=>"ami-08f41161",
100
+ # :spot_price=>0.01,
101
+ # :monitoring_enabled=>false,
102
+ # :key_name=>"tim"},
103
+ # {:type=>"one-time",
104
+ # :instance_id=>"i-c516ceae",
105
+ # :create_time=>"2010-03-10T10:43:48.000Z",
106
+ # :instance_type=>"c1.medium",
107
+ # :state=>"active",
108
+ # :groups=>["default", "33"],
109
+ # :product_description=>"Linux/UNIX",
110
+ # :spot_instance_request_id=>"sir-5eb6c604",
111
+ # :image_id=>"ami-08f41161",
112
+ # :spot_price=>0.2,
113
+ # :monitoring_enabled=>false,
114
+ # :key_name=>"tim"}]
115
+ #
116
+ def describe_spot_instance_requests(*spot_instance_request_ids)
117
+ link = generate_request("DescribeSpotInstanceRequests", amazonize_list('SpotInstanceRequestId', spot_instance_request_ids.flatten))
118
+ request_info(link, QEc2DescribeSpotInstanceParser.new(:logger => @logger))
119
+ end
120
+
121
+ # Create a Spot Instance request.
122
+ #
123
+ # Mandatory params: :image_id, :spot_price, :instance_type
124
+ # Optional params: :valid_from, :valid_until, :instance_count, :type, :launch_group,
125
+ # :availability_zone_group, :key_name, :user_data, :addressing_type, :kernel_id,
126
+ # :ramdisk_id, :subnet_id, :availability_zone, :monitoring_enabled, :groups,
127
+ # :block_device_mappings
128
+ #
129
+ # ec2.request_spot_instances(
130
+ # :image_id => 'ami-08f41161',
131
+ # :spot_price => 0.01,
132
+ # :key_name => 'tim',
133
+ # :instance_count => 2,
134
+ # :groups => ['33','default'],
135
+ # :instance_type => 'c1.medium') #=>
136
+ #
137
+ # [{:product_description=>"Linux/UNIX",
138
+ # :type=>"one-time",
139
+ # :spot_instance_requestId=>"sir-7a893003",
140
+ # :monitoring_enabled=>false,
141
+ # :image_id=>"ami-08f41161",
142
+ # :state=>"open",
143
+ # :spot_price=>0.01,
144
+ # :groups=>["default", "33"],
145
+ # :key_name=>"tim",
146
+ # :create_time=>"2010-03-10T10:33:09.000Z",
147
+ # :instance_type=>"c1.medium"},
148
+ # {:product_description=>"Linux/UNIX",
149
+ # :type=>"one-time",
150
+ # :spot_instance_requestId=>"sir-13dc9a03",
151
+ # :monitoring_enabled=>false,
152
+ # :image_id=>"ami-08f41161",
153
+ # :state=>"open",
154
+ # :spot_price=>0.01,
155
+ # :groups=>["default", "33"],
156
+ # :key_name=>"tim",
157
+ # :create_time=>"2010-03-10T10:33:09.000Z",
158
+ # :instance_type=>"c1.medium"}]
159
+ #
160
+ # ec2.request_spot_instances(
161
+ # :image_id => 'ami-08f41161',
162
+ # :spot_price => 0.01,
163
+ # :instance_type => 'm1.small',
164
+ # :valid_from => 10.minutes.since,
165
+ # :valid_until => 1.hour.since,
166
+ # :instance_count => 1,
167
+ # :key_name => 'tim',
168
+ # :groups => ['33','default'],
169
+ # :availability_zone => 'us-east-1a',
170
+ # :monitoring_enabled => true,
171
+ # :launch_group => 'lg1',
172
+ # :availability_zone_group => 'azg1',
173
+ # :block_device_mappings => [ { :device_name => '/dev/sdk',
174
+ # :ebs_snapshot_id => 'snap-145cbc7d',
175
+ # :ebs_delete_on_termination => true,
176
+ # :ebs_volume_size => 3,
177
+ # :virtual_name => 'ephemeral2'
178
+ # } ] ) #=>
179
+ #
180
+ # [{:monitoring_enabled=>true,
181
+ # :type=>"one-time",
182
+ # :image_id=>"ami-08f41161",
183
+ # :launch_group=>"lg1",
184
+ # :state=>"open",
185
+ # :valid_until=>"2010-02-05T19:13:44.000Z",
186
+ # :create_time=>"2010-02-05T18:13:46.000Z",
187
+ # :availability_zone_group=>"azg1",
188
+ # :spot_price=>0.01,
189
+ # :block_device_mappings=>
190
+ # [{:ebs_delete_on_termination=>true,
191
+ # :ebs_volume_size=>3,
192
+ # :virtual_name=>"ephemeral2",
193
+ # :device_name=>"/dev/sdk",
194
+ # :ebs_snapshot_id=>"snap-145cbc7d"}],
195
+ # :instance_type=>"m1.small",
196
+ # :groups=>["default", "33"],
197
+ # :product_description=>"Linux/UNIX",
198
+ # :key_name=>"tim",
199
+ # :valid_from=>"2010-02-05T18:23:44.000Z",
200
+ # :availability_zone=>"us-east-1a",
201
+ # :spot_instance_request_id=>"sir-32da8a03"}]
202
+ #
203
+ def request_spot_instances(options)
204
+ options = options.dup
205
+ request_hash = { 'SpotPrice' => options[:spot_price],
206
+ 'LaunchSpecification.ImageId' => options[:image_id],
207
+ 'LaunchSpecification.InstanceType' => options[:instance_type]}
208
+ request_hash['ValidFrom'] = AwsUtils::utc_iso8601(options[:valid_from]) unless options[:valid_from].blank?
209
+ request_hash['ValidUntil'] = AwsUtils::utc_iso8601(options[:valid_until]) unless options[:valid_until].blank?
210
+ request_hash['InstanceCount'] = options[:instance_count] unless options[:instance_count].blank?
211
+ request_hash['Type'] = options[:type] unless options[:type].blank?
212
+ request_hash['LaunchGroup'] = options[:launch_group] unless options[:launch_group].blank?
213
+ request_hash['AvailabilityZoneGroup'] = options[:availability_zone_group] unless options[:availability_zone_group].blank?
214
+ request_hash['LaunchSpecification.KeyName'] = options[:key_name] unless options[:key_name].blank?
215
+ request_hash['LaunchSpecification.AddressingType'] = options[:addressing_type] unless options[:addressing_type].blank?
216
+ request_hash['LaunchSpecification.KernelId'] = options[:kernel_id] unless options[:kernel_id].blank?
217
+ request_hash['LaunchSpecification.RamdiskId'] = options[:ramdisk_id] unless options[:ramdisk_id].blank?
218
+ request_hash['LaunchSpecification.SubnetId'] = options[:subnet_id] unless options[:subnet_id].blank?
219
+ request_hash['LaunchSpecification.Placement.AvailabilityZone'] = options[:availability_zone] unless options[:availability_zone].blank?
220
+ request_hash['LaunchSpecification.Monitoring.Enabled'] = options[:monitoring_enabled] unless options[:monitoring_enabled].blank?
221
+ request_hash.merge!(amazonize_list('LaunchSpecification.SecurityGroup', options[:groups])) unless options[:groups].blank?
222
+ request_hash.merge!(amazonize_block_device_mappings(options[:block_device_mappings], 'LaunchSpecification.BlockDeviceMapping'))
223
+ unless options[:user_data].blank?
224
+ # See RightAws::Ec2#run_instances
225
+ options[:user_data].strip!
226
+ request_hash['LaunchSpecification.UserData'] = Base64.encode64(options[:user_data]).delete("\n") unless options[:user_data].blank?
227
+ end
228
+ link = generate_request("RequestSpotInstances", request_hash)
229
+ request_info(link, QEc2DescribeSpotInstanceParser.new(:logger => @logger))
230
+ end
231
+
232
+ # Cancel one or more Spot Instance requests.
233
+ #
234
+ # ec2.cancel_spot_instance_requests('sir-60662c03',"sir-d3c96e04", "sir-4fa8d804","sir-6992ce04") #=>
235
+ # [{:state=>"cancelled", :spot_instance_request_id=>"sir-60662c03"},
236
+ # {:state=>"cancelled", :spot_instance_request_id=>"sir-6992ce04"},
237
+ # {:state=>"cancelled", :spot_instance_request_id=>"sir-4fa8d804"},
238
+ # {:state=>"cancelled", :spot_instance_request_id=>"sir-d3c96e04"}]
239
+ #
240
+ def cancel_spot_instance_requests(*spot_instance_request_ids)
241
+ link = generate_request("CancelSpotInstanceRequests", amazonize_list('SpotInstanceRequestId', spot_instance_request_ids.flatten))
242
+ request_info(link, QEc2CancelSpotInstanceParser.new(:logger => @logger))
243
+ end
244
+
245
+ # Create the data feed for Spot Instances
246
+ # (Enables to view Spot Instance usage logs)
247
+ #
248
+ # ec2.create_spot_datafeed_subscription('bucket-for-konstantin-eu', 'splogs/') #=>
249
+ # { :owner_id=>"826693181925",
250
+ # :bucket=>"bucket-for-konstantin-eu",
251
+ # :prefix=>"splogs/",
252
+ # :state=>"Active"}
253
+ #
254
+ def create_spot_datafeed_subscription(bucket, prefix=nil)
255
+ request_hash = { 'Bucket' => bucket }
256
+ request_hash['Prefix'] = prefix unless prefix.blank?
257
+ link = generate_request("CreateSpotDatafeedSubscription", request_hash)
258
+ request_info(link, QEc2DescribeSpotDatafeedSubscriptionParser.new(:logger => @logger))
259
+ end
260
+
261
+ # Describe the data feed for Spot Instances.
262
+ #
263
+ # ec2.describe_spot_datafeed_subscription #=>
264
+ # { :owner_id=>"826693181925",
265
+ # :bucket=>"bucket-for-konstantin-eu",
266
+ # :prefix=>"splogs/",
267
+ # :state=>"Active"}
268
+ #
269
+ def describe_spot_datafeed_subscription
270
+ link = generate_request("DescribeSpotDatafeedSubscription")
271
+ request_info(link, QEc2DescribeSpotDatafeedSubscriptionParser.new(:logger => @logger))
272
+ end
273
+
274
+ # Delete the data feed for Spot Instances.
275
+ #
276
+ # ec2.delete_spot_datafeed_subscription #=> true
277
+ #
278
+ def delete_spot_datafeed_subscription()
279
+ link = generate_request("DeleteSpotDatafeedSubscription")
280
+ request_info(link, RightBoolResponseParser.new(:logger => @logger))
281
+ end
282
+
283
+ #-----------------------------------------------------------------
284
+ # PARSERS: Spot Instances
285
+ #-----------------------------------------------------------------
286
+
287
+ class QEc2DescribeSpotPriceHistoryParser < RightAWSParser #:nodoc:
288
+ def tagstart(name, attributes)
289
+ @item = {} if name == 'item'
290
+ end
291
+ def tagend(name)
292
+ case name
293
+ when 'instanceType' then @item[:instance_type] = @text
294
+ when 'productDescription' then @item[:product_description] = @text
295
+ when 'spotPrice' then @item[:spot_price] = @text.to_f
296
+ when 'timestamp' then @item[:timestamp] = @text
297
+ when 'item' then @result << @item
298
+ end
299
+ end
300
+ def reset
301
+ @result = []
302
+ end
303
+ end
304
+
305
+ class QEc2DescribeSpotInstanceParser < RightAWSParser #:nodoc:
306
+ def tagstart(name, attributes)
307
+ case full_tag_name
308
+ when %r{spotInstanceRequestSet/item$}
309
+ @item = {}
310
+ when %r{/blockDeviceMapping/item$}
311
+ @item[:block_device_mappings] ||= []
312
+ @block_device_mapping = {}
313
+ end
314
+ end
315
+ def tagend(name)
316
+ case name
317
+ when 'spotInstanceRequestId' then @item[:spot_instance_request_id]= @text
318
+ when 'spotPrice' then @item[:spot_price] = @text.to_f
319
+ when 'type' then @item[:type] = @text
320
+ when 'state' then @item[:state] = @text
321
+ when 'code' then @item[:fault_code] = @text
322
+ when 'message' then @item[:fault_message] = @text
323
+ when 'validFrom' then @item[:valid_from] = @text
324
+ when 'validUntil' then @item[:valid_until] = @text
325
+ when 'launchGroup' then @item[:launch_group] = @text
326
+ when 'availabilityZoneGroup' then @item[:availability_zone_group] = @text
327
+ when 'imageId' then @item[:image_id] = @text
328
+ when 'keyName' then @item[:key_name] = @text
329
+ when 'userData' then @item[:userData] = @text
330
+ when 'data' then @item[:data] = @text
331
+ when 'addressingType' then @item[:addressing_type] = @text
332
+ when 'instanceType' then @item[:instance_type] = @text
333
+ when 'availabilityZone' then @item[:availability_zone] = @text
334
+ when 'kernelId' then @item[:kernel_id] = @text
335
+ when 'ramdiskId' then @item[:ramdisk_id] = @text
336
+ when 'subnetId' then @item[:subnet_id] = @text
337
+ when 'instanceId' then @item[:instance_id] = @text
338
+ when 'createTime' then @item[:create_time] = @text
339
+ when 'productDescription' then @item[:product_description] = @text
340
+ when 'groupId' then (@item[:groups] ||= []) << @text
341
+ else
342
+ case full_tag_name
343
+ when %r{monitoring/enabled$}
344
+ @item[:monitoring_enabled] = @text == 'true'
345
+ when %r{/blockDeviceMapping/item} # no trailing $
346
+ case name
347
+ when 'deviceName' then @block_device_mapping[:device_name] = @text
348
+ when 'virtualName' then @block_device_mapping[:virtual_name] = @text
349
+ when 'volumeSize' then @block_device_mapping[:ebs_volume_size] = @text.to_i
350
+ when 'snapshotId' then @block_device_mapping[:ebs_snapshot_id] = @text
351
+ when 'deleteOnTermination' then @block_device_mapping[:ebs_delete_on_termination] = @text == 'true' ? true : false
352
+ when 'item' then @item[:block_device_mappings] << @block_device_mapping
353
+ end
354
+ when %r{spotInstanceRequestSet/item$}
355
+ @result << @item
356
+ end
357
+ end
358
+ end
359
+ def reset
360
+ @result = []
361
+ end
362
+ end
363
+
364
+ class QEc2CancelSpotInstanceParser < RightAWSParser #:nodoc:
365
+ def tagstart(name, attributes)
366
+ @item = {} if name == 'item'
367
+ end
368
+ def tagend(name)
369
+ case name
370
+ when 'spotInstanceRequestId' then @item[:spot_instance_request_id] = @text
371
+ when 'state' then @item[:state] = @text
372
+ when 'item' then @result << @item
373
+ end
374
+ end
375
+ def reset
376
+ @result = []
377
+ end
378
+ end
379
+
380
+ class QEc2DescribeSpotDatafeedSubscriptionParser < RightAWSParser #:nodoc:
381
+ def tagend(name)
382
+ case name
383
+ when 'ownerId' then @result[:owner_id] = @text
384
+ when 'bucket' then @result[:bucket] = @text
385
+ when 'prefix' then @result[:prefix] = @text
386
+ when 'state' then @result[:state] = @text
387
+ when 'fault' then @result[:fault] = @text
388
+ when 'code' then @result[:code] = @text
389
+ when 'message' then @result[:message] = @text
390
+ end
391
+ end
392
+ def reset
393
+ @result = {}
394
+ end
395
+ end
396
+
397
+ end
398
+
399
+ end
@@ -0,0 +1,571 @@
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
+ private
29
+
30
+ def vpc__split_list_and_filters(*params) # :nodoc:
31
+ params = params.flatten
32
+ filters = params.last.is_a?(Hash) ? params.pop : {}
33
+ # Make values to be arrays.
34
+ filters.each{|key, values| filters[key] = Array(values) }
35
+ [params, filters]
36
+ end
37
+
38
+ public
39
+
40
+ #-----------------
41
+ # VPC
42
+ #-----------------
43
+
44
+ # Describe VPCs
45
+ #
46
+ # ec2.describe_vpcs #=>
47
+ # [{:vpc_id=>"vpc-890ce2e0",
48
+ # :dhcp_options_id=>"default",
49
+ # :cidr_block=>"10.0.0.0/23",
50
+ # :state=>"available"}]
51
+ #
52
+ # ec2.describe_vpcs("vpc-890ce2e0")
53
+ #
54
+ def describe_vpcs(*list_and_filters)
55
+ list, filters = vpc__split_list_and_filters(list_and_filters)
56
+ cache_for = (list.empty? && filters.empty?) ? :describe_vpcs : nil
57
+ request_hash = {}
58
+ request_hash.merge!(amazonize_list('VpcId', list))
59
+ request_hash.merge!(amazonize_list(['Filter.?.Key','Filter.?.Value.?'], filters))
60
+ link = generate_request("DescribeVpcs", request_hash)
61
+ request_cache_or_info cache_for, link, QEc2DescribeVpcsParser, @@bench, cache_for
62
+ rescue Exception
63
+ on_exception
64
+ end
65
+
66
+ # Create VPC.
67
+ #
68
+ # ec2.create_vpc('10.0.0.0/23') #=>
69
+ # {:vpc_id=>"vpc-890ce2e0",
70
+ # :dhcp_options_id=>"default",
71
+ # :cidr_block=>"10.0.0.0/23",
72
+ # :state=>"pending"}
73
+ #
74
+ def create_vpc(cidr_block)
75
+ link = generate_request("CreateVpc",'CidrBlock' => cidr_block )
76
+ request_info(link, QEc2DescribeVpcsParser.new(:logger => @logger)).first
77
+ rescue Exception
78
+ on_exception
79
+ end
80
+
81
+ # Delete VPC.
82
+ #
83
+ # ec2.delete_vpc("vpc-890ce2e0") #=> true
84
+ #
85
+ def delete_vpc(vpc_id)
86
+ link = generate_request("DeleteVpc", 'VpcId' => vpc_id )
87
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
88
+ rescue Exception
89
+ on_exception
90
+ end
91
+
92
+ #-----------------
93
+ # Subnets
94
+ #-----------------
95
+
96
+ # Describe Subnet.
97
+ #
98
+ # ec2.describe_subnets #=>
99
+ # [{:available_ip_address_count=>"251",
100
+ # :vpc_id=>"vpc-890ce2e0",
101
+ # :availability_zone=>"us-east-1a",
102
+ # :subnet_id=>"subnet-770de31e",
103
+ # :cidr_block=>"10.0.1.0/24",
104
+ # :state=>"available"}]
105
+ #
106
+ def describe_subnets(*list_and_filters)
107
+ list, filters = vpc__split_list_and_filters(list_and_filters)
108
+ cache_for = (list.empty? && filters.empty?) ? :describe_subnets : nil
109
+ request_hash = {}
110
+ request_hash.merge!(amazonize_list('SubnetId', list))
111
+ request_hash.merge!(amazonize_list(['Filter.?.Key','Filter.?.Value.?'], filters))
112
+ link = generate_request("DescribeSubnets", request_hash)
113
+ request_cache_or_info cache_for, link, QEc2DescribeSubnetsParser, @@bench, cache_for
114
+ rescue Exception
115
+ on_exception
116
+ end
117
+
118
+ # Create Subnet.
119
+ #
120
+ # ec2.create_subnet("vpc-890ce2e0",'10.0.1.0/24') #=>
121
+ # {:available_ip_address_count=>"251",
122
+ # :vpc_id=>"vpc-890ce2e0",
123
+ # :availability_zone=>"us-east-1a",
124
+ # :subnet_id=>"subnet-770de31e",
125
+ # :cidr_block=>"10.0.1.0/24",
126
+ # :state=>"pending"}
127
+ #
128
+ def create_subnet(vpc_id, cidr_block, availability_zone = nil)
129
+ request_hash = { 'VpcId' => vpc_id,
130
+ 'CidrBlock' => cidr_block }
131
+ request_hash['AvailabilityZone'] = availability_zone unless availability_zone.blank?
132
+ link = generate_request("CreateSubnet", request_hash)
133
+ request_info(link, QEc2DescribeSubnetsParser.new(:logger => @logger)).first
134
+ rescue Exception
135
+ on_exception
136
+ end
137
+
138
+ # Delete Subnet.
139
+ #
140
+ # ec2.delete_subnet("subnet-770de31e") #=> true
141
+ #
142
+ def delete_subnet(subnet_id)
143
+ link = generate_request("DeleteSubnet", 'SubnetId' => subnet_id )
144
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
145
+ rescue Exception
146
+ on_exception
147
+ end
148
+
149
+ #-----------------
150
+ # DHCP Options
151
+ #-----------------
152
+
153
+ # Describe DHCP options.
154
+ #
155
+ # ec2.describe_dhcp_options #=>
156
+ # [{:dhcp_options_id=>"dopt-cb0de3a2",
157
+ # :dhcp_configuration_set=>
158
+ # {"netbios-node-type"=>["1"], "domain-name"=>["my.awesomesite.ru"]}}]
159
+ #
160
+ def describe_dhcp_options(*list)
161
+ list = list.flatten
162
+ cache_for = list.empty? ? :describe_dhcp_options : nil
163
+ request_hash = amazonize_list('DhcpOptionsId', list)
164
+ link = generate_request("DescribeDhcpOptions", request_hash)
165
+ request_cache_or_info cache_for, link, QEc2DescribeDhcpOptionsParser, @@bench, cache_for
166
+ rescue Exception
167
+ on_exception
168
+ end
169
+
170
+ # Create DHCP options.
171
+ #
172
+ # ec2.create_dhcp_options('domain-name' => 'my.awesomesite.ru',
173
+ # 'netbios-node-type' => 1) #=>
174
+ # {:dhcp_options_id=>"dopt-cb0de3a2",
175
+ # :dhcp_configuration_set=>
176
+ # {"netbios-node-type"=>["1"], "domain-name"=>["my.awesomesite.ru"]}}
177
+ #
178
+ def create_dhcp_options(dhcp_configuration)
179
+ dhcp_configuration.each{ |key, values| dhcp_configuration[key] = Array(values) }
180
+ request_hash = amazonize_list(['DhcpConfiguration.?.Key','DhcpConfiguration.?.Value.?'], dhcp_configuration)
181
+ link = generate_request("CreateDhcpOptions", request_hash)
182
+ request_info(link, QEc2DescribeDhcpOptionsParser.new(:logger => @logger)).first
183
+ rescue Exception
184
+ on_exception
185
+ end
186
+
187
+ # Associate DHCP options
188
+ #
189
+ # ec2.associate_dhcp_options("dopt-cb0de3a2", "vpc-890ce2e0" ) #=> true
190
+ # ec2.describe_vpcs #=>
191
+ # [{:vpc_id=>"vpc-890ce2e0",
192
+ # :dhcp_options_id=>"dopt-cb0de3a2",
193
+ # :cidr_block=>"10.0.0.0/23",
194
+ # :state=>"available"}]
195
+ #
196
+ def associate_dhcp_options(dhcp_options_id, vpc_id)
197
+ link = generate_request("AssociateDhcpOptions", 'DhcpOptionsId' => dhcp_options_id,
198
+ 'VpcId' => vpc_id)
199
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
200
+ rescue Exception
201
+ on_exception
202
+ end
203
+
204
+ # Delete DHCP Options.
205
+ #
206
+ # ec2.delete_dhcp_options("dopt-cb0de3a2") #=> true
207
+ #
208
+ def delete_dhcp_options(dhcp_options_id)
209
+ link = generate_request("DeleteDhcpOptions", 'DhcpOptionsId' => dhcp_options_id )
210
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
211
+ rescue Exception
212
+ on_exception
213
+ end
214
+
215
+ #-----------------
216
+ # Customer Gateways
217
+ #-----------------
218
+
219
+ # Describe customer gateways.
220
+ #
221
+ # ec2.describe_customer_gateways
222
+ #
223
+ # [{:type=>"ipsec.1",
224
+ # :ip_address=>"12.1.2.3",
225
+ # :bgp_asn=>"65534",
226
+ # :state=>"available",
227
+ # :customer_gateway_id=>"cgw-d5a643bc"}]
228
+ #
229
+ def describe_customer_gateways(*list_and_filters)
230
+ list, filters = vpc__split_list_and_filters(list_and_filters)
231
+ cache_for = (list.empty? && filters.empty?) ? :describe_customer_gateways : nil
232
+ request_hash = {}
233
+ request_hash.merge!(amazonize_list('CustomerGatewayId', list))
234
+ request_hash.merge!(amazonize_list(['Filter.?.Key','Filter.?.Value.?'], filters))
235
+ link = generate_request("DescribeCustomerGateways", request_hash)
236
+ request_cache_or_info cache_for, link, QEc2DescribeCustomerGatewaysParser, @@bench, cache_for
237
+ rescue Exception
238
+ on_exception
239
+ end
240
+
241
+ # Create customer gateway.
242
+ #
243
+ # ec2.create_customer_gateway('ipsec.1', '12.1.2.3', 65534) #=>
244
+ # {:type=>"ipsec.1",
245
+ # :bgp_asn=>"65534",
246
+ # :ip_address=>"12.1.2.3",
247
+ # :state=>"pending",
248
+ # :customer_gateway_id=>"cgw-d5a643bc"}
249
+ #
250
+ def create_customer_gateway(type, ip_address, bgp_asn)
251
+ link = generate_request("CreateCustomerGateway", 'Type' => type,
252
+ 'IpAddress' => ip_address,
253
+ 'BgpAsn' => bgp_asn )
254
+ request_info(link, QEc2DescribeCustomerGatewaysParser.new(:logger => @logger)).first
255
+ rescue Exception
256
+ on_exception
257
+ end
258
+
259
+ # Delete customer gateway.
260
+ #
261
+ # ec2.delete_customer_gateway("cgw-d5a643bc") #=> true
262
+ #
263
+ def delete_customer_gateway(customer_gateway_id)
264
+ link = generate_request("DeleteCustomerGateway", 'CustomerGatewayId' => customer_gateway_id )
265
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
266
+ rescue Exception
267
+ on_exception
268
+ end
269
+
270
+ #-----------------
271
+ # VPN Gateways
272
+ #-----------------
273
+
274
+ # Describe VPN gateways.
275
+ #
276
+ # ec2.describe_vpn_gateways #=>
277
+ # [{:type=>"ipsec.1",
278
+ # :availability_zone=>"us-east-1a",
279
+ # :attachments=>[{:vpc_id=>"vpc-890ce2e0", :state=>"attached"}],
280
+ # :vpn_gateway_id=>"vgw-dfa144b6"}]
281
+ #
282
+ def describe_vpn_gateways(*list_and_filters)
283
+ list, filters = vpc__split_list_and_filters(list_and_filters)
284
+ cache_for = (list.empty? && filters.empty?) ? :describe_vpn_gateways : nil
285
+ request_hash = {}
286
+ request_hash.merge!(amazonize_list('VpnGatewayId', list))
287
+ request_hash.merge!(amazonize_list(['Filter.?.Key','Filter.?.Value.?'], filters))
288
+ link = generate_request("DescribeVpnGateways", request_hash)
289
+ request_cache_or_info cache_for, link, QEc2DescribeVpnGatewaysParser, @@bench, cache_for
290
+ rescue Exception
291
+ on_exception
292
+ end
293
+
294
+ # Create VPN gateway.
295
+ #
296
+ # ec2.create_vpn_gateway('ipsec.1') #=>
297
+ # {:type=>"ipsec.1",
298
+ # :availability_zone=>"us-east-1a",
299
+ # :attachments=>[nil],
300
+ # :vpn_gateway_id=>"vgw-dfa144b6"}
301
+ #
302
+ def create_vpn_gateway(type, availability_zone=nil)
303
+ request_hash = { 'Type' => type }
304
+ request_hash['AvailabilityZone'] = availability_zone unless availability_zone.blank?
305
+ link = generate_request("CreateVpnGateway", request_hash )
306
+ request_info(link, QEc2DescribeVpnGatewaysParser.new(:logger => @logger)).first
307
+ rescue Exception
308
+ on_exception
309
+ end
310
+
311
+ # Attach VPN gateway.
312
+ #
313
+ # ec2.attach_vpn_gateway('vgw-dfa144b6','vpc-890ce2e0') #=>
314
+ # {:vpc_id=>"vpc-890ce2e0", :state=>"attaching"}
315
+ #
316
+ def attach_vpn_gateway(vpn_gateway_id, vpc_id)
317
+ link = generate_request("AttachVpnGateway", 'VpnGatewayId' => vpn_gateway_id,
318
+ 'VpcId' => vpc_id )
319
+ request_info(link, QEc2AttachVpnGatewayParser.new(:logger => @logger))
320
+ rescue Exception
321
+ on_exception
322
+ end
323
+
324
+ # Detach VPN gateway.
325
+ #
326
+ # ec2.detach_vpn_gateway('vgw-dfa144b6','vpc-890ce2e0') #=> true
327
+ #
328
+ def detach_vpn_gateway(vpn_gateway_id, vpc_id)
329
+ link = generate_request("DetachVpnGateway", 'VpnGatewayId' => vpn_gateway_id,
330
+ 'VpcId' => vpc_id )
331
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
332
+ rescue Exception
333
+ on_exception
334
+ end
335
+
336
+ # Delete vpn gateway.
337
+ #
338
+ # ec2.delete_vpn_gateway("vgw-dfa144b6") #=> true
339
+ #
340
+ def delete_vpn_gateway(vpn_gateway_id)
341
+ link = generate_request("DeleteVpnGateway", 'VpnGatewayId' => vpn_gateway_id )
342
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
343
+ rescue Exception
344
+ on_exception
345
+ end
346
+
347
+ #-----------------
348
+ # VPN Connections
349
+ #-----------------
350
+
351
+ # Describe VPN connections.
352
+ #
353
+ # ec2.describe_vpn_connections #=>
354
+ # [{:type=>"ipsec.1",
355
+ # :vpn_connection_id=>"vpn-a9a643c0",
356
+ # :customer_gateway_configuration=>
357
+ # "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<vpn_connection id=\"vpn-a9a643c0\">\n...</vpn_connection>\n",
358
+ # :state=>"available",
359
+ # :vpn_gateway_id=>"vgw-dfa144b6",
360
+ # :customer_gateway_id=>"cgw-81a643e8"}]
361
+ #
362
+ def describe_vpn_connections(*list_and_filters)
363
+ list, filters = vpc__split_list_and_filters(list_and_filters)
364
+ cache_for = (list.empty? && filters.empty?) ? :describe_vpn_connections : nil
365
+ request_hash = {}
366
+ request_hash.merge!(amazonize_list('VpnConnectionId', list))
367
+ request_hash.merge!(amazonize_list(['Filter.?.Key','Filter.?.Value.?'], filters))
368
+ link = generate_request("DescribeVpnConnections", request_hash)
369
+ request_cache_or_info cache_for, link, QEc2DescribeVpnConnectionsParser, @@bench, cache_for
370
+ rescue Exception
371
+ on_exception
372
+ end
373
+
374
+ # Create VPN connection.
375
+ #
376
+ # ec2.create_vpn_connection('ipsec.1', 'cgw-81a643e8' ,'vgw-dfa144b6')
377
+ # {:customer_gateway_id=>"cgw-81a643e8",
378
+ # :vpn_connection_id=>"vpn-a9a643c0",
379
+ # :customer_gateway_configuration=>
380
+ # "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<vpn_connection id=\"vpn-a9a643c0\">\n...</vpn_connection>\n",
381
+ # :state=>"pending",
382
+ # :vpn_gateway_id=>"vgw-dfa144b6"}
383
+ #
384
+ def create_vpn_connection(type, customer_gateway_id, vpn_gateway_id)
385
+ link = generate_request("CreateVpnConnection", 'Type' => type,
386
+ 'CustomerGatewayId' => customer_gateway_id,
387
+ 'VpnGatewayId' => vpn_gateway_id )
388
+ request_info(link, QEc2DescribeVpnConnectionsParser.new(:logger => @logger)).first
389
+ rescue Exception
390
+ on_exception
391
+ end
392
+
393
+ # Delete VPN connection.
394
+ #
395
+ # ec2.delete_vpn_connection("vpn-a9a643c0") #=> true
396
+ #
397
+ def delete_vpn_connection(vpn_connection_id)
398
+ link = generate_request("DeleteVpnConnection", 'VpnConnectionId' => vpn_connection_id )
399
+ request_info(link, RightHttp2xxParser.new(:logger => @logger))
400
+ rescue Exception
401
+ on_exception
402
+ end
403
+
404
+ #-----------------
405
+ # Parsers
406
+ #-----------------
407
+
408
+ class QEc2DescribeVpcsParser < RightAWSParser #:nodoc:
409
+ def tagstart(name, attributes)
410
+ case name
411
+ when 'item', 'vpc' then @item = {}
412
+ end
413
+ end
414
+ def tagend(name)
415
+ case name
416
+ when 'vpcId' then @item[:vpc_id] = @text
417
+ when 'state' then @item[:state] = @text
418
+ when 'dhcpOptionsId' then @item[:dhcp_options_id] = @text
419
+ when 'cidrBlock' then @item[:cidr_block] = @text
420
+ when 'item', 'vpc' then @result << @item
421
+ end
422
+ end
423
+ def reset
424
+ @result = []
425
+ end
426
+ end
427
+
428
+ class QEc2DescribeSubnetsParser < RightAWSParser #:nodoc:
429
+ def tagstart(name, attributes)
430
+ case name
431
+ when 'item', 'subnet' then @item = {}
432
+ end
433
+ end
434
+ def tagend(name)
435
+ case name
436
+ when 'subnetId' then @item[:subnet_id] = @text
437
+ when 'state' then @item[:state] = @text
438
+ when 'vpcId' then @item[:vpc_id] = @text
439
+ when 'cidrBlock' then @item[:cidr_block] = @text
440
+ when 'availabilityZone' then @item[:availability_zone] = @text
441
+ when 'availableIpAddressCount' then @item[:available_ip_address_count] = @text
442
+ when 'item', 'subnet' then @result << @item
443
+ end
444
+ end
445
+ def reset
446
+ @result = []
447
+ end
448
+ end
449
+
450
+ class QEc2DescribeDhcpOptionsParser < RightAWSParser #:nodoc:
451
+ def tagstart(name, attributes)
452
+ case full_tag_name
453
+ when @p1, @p2
454
+ @item = { :dhcp_configuration_set => {} }
455
+ end
456
+ end
457
+ def tagend(name)
458
+ case name
459
+ when 'dhcpOptionsId' then @item[:dhcp_options_id] = @text
460
+ when 'key' then @conf_item_key = @text
461
+ when 'value' then (@item[:dhcp_configuration_set][@conf_item_key] ||= []) << @text
462
+ end
463
+ case full_tag_name
464
+ when @p1, @p2
465
+ @result << @item
466
+ end
467
+ end
468
+ def reset
469
+ @p1 = 'DescribeDhcpOptionsResponse/dhcpOptionsSet/item'
470
+ @p2 = 'CreateDhcpOptionsResponse/dhcpOptions'
471
+ @result = []
472
+ end
473
+ end
474
+
475
+ class QEc2DescribeCustomerGatewaysParser < RightAWSParser #:nodoc:
476
+ def tagstart(name, attributes)
477
+ case name
478
+ when 'item', 'customerGateway'
479
+ @item = {}
480
+ end
481
+ end
482
+ def tagend(name)
483
+ case name
484
+ when 'customerGatewayId' then @item[:customer_gateway_id] = @text
485
+ when 'state' then @item[:state] = @text
486
+ when 'type' then @item[:type] = @text
487
+ when 'ipAddress' then @item[:ip_address] = @text
488
+ when 'bgpAsn' then @item[:bgp_asn] = @text
489
+ when 'item', 'customerGateway' then @result << @item
490
+ end
491
+ end
492
+ def reset
493
+ @result = []
494
+ end
495
+ end
496
+
497
+ class QEc2DescribeVpnGatewaysParser < RightAWSParser #:nodoc:
498
+ def tagstart(name, attributes)
499
+ case full_tag_name
500
+ when @p1, @p2
501
+ @item = { :attachments => [] }
502
+ when "#{@p1}/attachments/item",
503
+ "#{@p2}/attachments/item"
504
+ @attachment = {}
505
+ end
506
+ end
507
+ def tagend(name)
508
+ case name
509
+ when 'vpnGatewayId' then @item[:vpn_gateway_id] = @text
510
+ when 'availabilityZone' then @item[:availability_zone] = @text
511
+ when 'type' then @item[:type] = @text
512
+ when 'vpcId' then @attachment[:vpc_id] = @text
513
+ end
514
+ case full_tag_name
515
+ when "#{@p1}/state",
516
+ "#{@p2}/state"
517
+ @item[:state] = @text
518
+ when "#{@p1}/attachments/item/state",
519
+ "#{@p2}/attachments/item/state"
520
+ @attachment[:state] = @text
521
+ when "#{@p1}/attachments/item",
522
+ "#{@p2}/attachments/item"
523
+ @item[:attachments] << @attachment unless @attachment.blank?
524
+ when @p1, @p2
525
+ @result << @item
526
+ end
527
+ end
528
+ def reset
529
+ @p1 = 'DescribeVpnGatewaysResponse/vpnGatewaySet/item'
530
+ @p2 = 'CreateVpnGatewayResponse/vpnGateway'
531
+ @result = []
532
+ end
533
+ end
534
+
535
+ class QEc2AttachVpnGatewayParser < RightAWSParser #:nodoc:
536
+ def tagend(name)
537
+ case name
538
+ when 'vpcId' then @result[:vpc_id] = @text
539
+ when 'state' then @result[:state] = @text
540
+ end
541
+ end
542
+ def reset
543
+ @result = {}
544
+ end
545
+ end
546
+
547
+
548
+ class QEc2DescribeVpnConnectionsParser < RightAWSParser #:nodoc:
549
+ def tagstart(name, attributes)
550
+ case name
551
+ when 'item', 'vpnConnection' then @item = {}
552
+ end
553
+ end
554
+ def tagend(name)
555
+ case name
556
+ when 'vpnConnectionId' then @item[:vpn_connection_id] = @text
557
+ when 'state' then @item[:state] = @text
558
+ when 'type' then @item[:type] = @text
559
+ when 'vpnGatewayId' then @item[:vpn_gateway_id] = @text
560
+ when 'customerGatewayId' then @item[:customer_gateway_id] = @text
561
+ when 'customerGatewayConfiguration' then @item[:customer_gateway_configuration] = @text
562
+ when 'item','vpnConnection' then @result << @item
563
+ end
564
+ end
565
+ def reset
566
+ @result = []
567
+ end
568
+ end
569
+
570
+ end
571
+ end