right_aws 1.9.0 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/History.txt +164 -13
  2. data/Manifest.txt +28 -1
  3. data/README.txt +12 -10
  4. data/Rakefile +56 -29
  5. data/lib/acf/right_acf_interface.rb +343 -172
  6. data/lib/acf/right_acf_invalidations.rb +144 -0
  7. data/lib/acf/right_acf_origin_access_identities.rb +230 -0
  8. data/lib/acf/right_acf_streaming_interface.rb +229 -0
  9. data/lib/acw/right_acw_interface.rb +248 -0
  10. data/lib/as/right_as_interface.rb +698 -0
  11. data/lib/awsbase/right_awsbase.rb +755 -115
  12. data/lib/awsbase/support.rb +2 -78
  13. data/lib/awsbase/version.rb +9 -0
  14. data/lib/ec2/right_ec2.rb +274 -1294
  15. data/lib/ec2/right_ec2_ebs.rb +514 -0
  16. data/lib/ec2/right_ec2_images.rb +444 -0
  17. data/lib/ec2/right_ec2_instances.rb +797 -0
  18. data/lib/ec2/right_ec2_monitoring.rb +70 -0
  19. data/lib/ec2/right_ec2_placement_groups.rb +108 -0
  20. data/lib/ec2/right_ec2_reserved_instances.rb +243 -0
  21. data/lib/ec2/right_ec2_security_groups.rb +496 -0
  22. data/lib/ec2/right_ec2_spot_instances.rb +422 -0
  23. data/lib/ec2/right_ec2_tags.rb +139 -0
  24. data/lib/ec2/right_ec2_vpc.rb +598 -0
  25. data/lib/ec2/right_ec2_vpc2.rb +382 -0
  26. data/lib/ec2/right_ec2_windows_mobility.rb +84 -0
  27. data/lib/elb/right_elb_interface.rb +573 -0
  28. data/lib/emr/right_emr_interface.rb +728 -0
  29. data/lib/iam/right_iam_access_keys.rb +71 -0
  30. data/lib/iam/right_iam_groups.rb +195 -0
  31. data/lib/iam/right_iam_interface.rb +341 -0
  32. data/lib/iam/right_iam_mfa_devices.rb +67 -0
  33. data/lib/iam/right_iam_users.rb +251 -0
  34. data/lib/rds/right_rds_interface.rb +1657 -0
  35. data/lib/right_aws.rb +30 -13
  36. data/lib/route_53/right_route_53_interface.rb +641 -0
  37. data/lib/s3/right_s3.rb +108 -41
  38. data/lib/s3/right_s3_interface.rb +349 -118
  39. data/lib/sdb/active_sdb.rb +388 -54
  40. data/lib/sdb/right_sdb_interface.rb +323 -64
  41. data/lib/sns/right_sns_interface.rb +286 -0
  42. data/lib/sqs/right_sqs.rb +1 -2
  43. data/lib/sqs/right_sqs_gen2.rb +73 -17
  44. data/lib/sqs/right_sqs_gen2_interface.rb +146 -73
  45. data/lib/sqs/right_sqs_interface.rb +12 -22
  46. data/right_aws.gemspec +91 -0
  47. data/test/README.mdown +39 -0
  48. data/test/acf/test_right_acf.rb +11 -19
  49. data/test/awsbase/test_helper.rb +2 -0
  50. data/test/awsbase/test_right_awsbase.rb +11 -0
  51. data/test/ec2/test_right_ec2.rb +32 -1
  52. data/test/elb/test_helper.rb +2 -0
  53. data/test/elb/test_right_elb.rb +43 -0
  54. data/test/rds/test_helper.rb +2 -0
  55. data/test/rds/test_right_rds.rb +120 -0
  56. data/test/route_53/fixtures/a_record.xml +18 -0
  57. data/test/route_53/fixtures/alias_record.xml +18 -0
  58. data/test/route_53/test_helper.rb +2 -0
  59. data/test/route_53/test_right_route_53.rb +141 -0
  60. data/test/s3/test_right_s3.rb +176 -42
  61. data/test/s3/test_right_s3_stubbed.rb +6 -4
  62. data/test/sdb/test_active_sdb.rb +120 -19
  63. data/test/sdb/test_batch_put_attributes.rb +54 -0
  64. data/test/sdb/test_right_sdb.rb +71 -16
  65. data/test/sns/test_helper.rb +2 -0
  66. data/test/sns/test_right_sns.rb +153 -0
  67. data/test/sqs/test_right_sqs.rb +0 -6
  68. data/test/sqs/test_right_sqs_gen2.rb +104 -49
  69. data/test/ts_right_aws.rb +1 -0
  70. metadata +181 -22
data/lib/right_aws.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2007-2008 RightScale Inc
2
+ # Copyright (c) 2007-2011 RightScale Inc
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -35,10 +35,27 @@ require 'rubygems'
35
35
  require 'right_http_connection'
36
36
 
37
37
  $:.unshift(File.dirname(__FILE__))
38
- require 'awsbase/benchmark_fix'
38
+ require 'awsbase/version'
39
39
  require 'awsbase/support'
40
+ require 'awsbase/benchmark_fix'
40
41
  require 'awsbase/right_awsbase'
41
42
  require 'ec2/right_ec2'
43
+ require 'ec2/right_ec2_images'
44
+ require 'ec2/right_ec2_instances'
45
+ require 'ec2/right_ec2_security_groups'
46
+ require 'ec2/right_ec2_spot_instances'
47
+ require 'ec2/right_ec2_ebs'
48
+ require 'ec2/right_ec2_reserved_instances'
49
+ require 'ec2/right_ec2_vpc'
50
+ require 'ec2/right_ec2_vpc2'
51
+ require 'ec2/right_ec2_monitoring'
52
+ require 'ec2/right_ec2_placement_groups'
53
+ require 'ec2/right_ec2_windows_mobility'
54
+ require 'ec2/right_ec2_tags'
55
+ require 'elb/right_elb_interface'
56
+ require 'emr/right_emr_interface'
57
+ require 'acw/right_acw_interface'
58
+ require 'as/right_as_interface'
42
59
  require 's3/right_s3_interface'
43
60
  require 's3/right_s3'
44
61
  require 'sqs/right_sqs_interface'
@@ -47,17 +64,17 @@ require 'sqs/right_sqs_gen2_interface'
47
64
  require 'sqs/right_sqs_gen2'
48
65
  require 'sdb/right_sdb_interface'
49
66
  require 'acf/right_acf_interface'
50
-
51
-
52
- module RightAws #:nodoc:
53
- module VERSION #:nodoc:
54
- MAJOR = 1
55
- MINOR = 9
56
- TINY = 0
57
-
58
- STRING = [MAJOR, MINOR, TINY].join('.')
59
- end
60
- end
67
+ require 'acf/right_acf_streaming_interface'
68
+ require 'acf/right_acf_origin_access_identities'
69
+ require 'acf/right_acf_invalidations'
70
+ require 'rds/right_rds_interface'
71
+ require 'iam/right_iam_interface'
72
+ require 'iam/right_iam_groups'
73
+ require 'iam/right_iam_users'
74
+ require 'iam/right_iam_access_keys'
75
+ require 'iam/right_iam_mfa_devices'
76
+ require 'route_53/right_route_53_interface'
77
+ require 'sns/right_sns_interface'
61
78
 
62
79
  #-
63
80
 
@@ -0,0 +1,641 @@
1
+ # Copyright (c) 2007-2011 RightScale Inc
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+ #
22
+
23
+ module RightAws
24
+
25
+ # = RightAws::Route53Interface -- Amazon Route 53 web service interface.
26
+ #
27
+ # The RightAws::Route53Interface class provides a complete interface to Amazon Route 53: a web
28
+ # service that enables you to manage your DNS service.
29
+ #
30
+ # For explanations of the semantics of each call, please refer to Amazon's documentation at
31
+ # http://aws.amazon.com/documentation/route53/
32
+ #
33
+ # Examples:
34
+ #
35
+ # # Create Route53 handler
36
+ # r53 = RightAws::Route53Interface.new(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
37
+ #
38
+ # #------------------------
39
+ # # Create Hosted Zone
40
+ # #------------------------
41
+ #
42
+ # hosted_zone_config = {
43
+ # :name => 'my-awesome-site.com.',
44
+ # :config => {
45
+ # :comment => 'My test site!'
46
+ # }
47
+ # }
48
+ # r53.create_hosted_zone(hosted_zone_config) #=>
49
+ # {:name_servers=>
50
+ # ["ns-1115.awsdns-11.org",
51
+ # "ns-696.awsdns-23.net",
52
+ # "ns-1963.awsdns-53.co.uk",
53
+ # "ns-362.awsdns-45.com"],
54
+ # :aws_id=>"/hostedzone/Z1K6NCF0EB26FB",
55
+ # :caller_reference=>"1295424990-710392-gqMuw-KcY8F-LFlrB-SQhp9",
56
+ # :config=>{:comment=>"My test site!"},
57
+ # :change_info=>
58
+ # {:status=>"PENDING",
59
+ # :aws_id=>"/change/C23QGMT8XTCAJY",
60
+ # :submitted_at=>"2011-01-19T08:16:31.046Z"},
61
+ # :name=>"my-awesome-site.com."}
62
+ #
63
+ # # List Hosted Zones
64
+ # r53.list_hosted_zones #=> []
65
+ # [{:aws_id=>"/hostedzone/Z1K6NCF0EB26FB",
66
+ # :caller_reference=>"1295424990-710392-gqMuw-KcY8F-LFlrB-SQhp9",
67
+ # :config=>{:comment=>"My test site!"},
68
+ # :name=>"my-awesome-site.com."}]
69
+ #
70
+ # #--------------------------------
71
+ # # Manage DNS Records and Changes
72
+ # #--------------------------------
73
+ #
74
+ # # Create DNS Records
75
+ # resource_record_sets = [ { :name => 'www1.my-awesome-site.com.',
76
+ # :type => 'NS',
77
+ # :ttl => 600,
78
+ # :resource_records => 'www.mysite.com' },
79
+ # { :name => 'www2.my-awesome-site.com.',
80
+ # :type => 'A',
81
+ # :ttl => 600,
82
+ # :resource_records => ['10.0.0.1'] } ]
83
+ # r53.create_resource_record_sets("/hostedzone/Z1K6NCF0EB26FB", resource_record_sets, 'my first set of records') #=>
84
+ # { :status=>"PENDING",
85
+ # :aws_id=>"/change/C2C6IGNRTKA0AY",
86
+ # :submitted_at=>"2011-01-19T08:29:26.160Z" }
87
+ #
88
+ # # Delete DNS records
89
+ # r53.delete_resource_record_sets("/hostedzone/Z1K6NCF0EB26FB", resource_record_sets, 'I dont need them any more') #=>
90
+ # { :status=>"PENDING",
91
+ # :aws_id=>"/change/C1CYJ10EZBFLO7",
92
+ # :submitted_at=>"2011-01-19T08:26:41.220Z" }
93
+ #
94
+ # # Create or delete DNS records (:action key must be provided):
95
+ # resource_record_sets = [ { :action => :create,
96
+ # :name => 'www1.my-awesome-site.com.',
97
+ # :type => 'NS',
98
+ # :ttl => 600,
99
+ # :resource_records => 'www.mysite.com' },
100
+ # { :action => :delete,
101
+ # :name => 'www2.my-awesome-site.com.',
102
+ # :type => 'A',
103
+ # :ttl => 600,
104
+ # :resource_records => ['10.0.0.1'] } ]
105
+ # r53.change_resource_record_sets("/hostedzone/Z1K6NCF0EB26FB", resource_record_sets, 'do change records')
106
+ # { :status=>"PENDING",
107
+ # :aws_id=>"/change/C2PWXVECN794LK",
108
+ # :submitted_at=>"2011-01-19T08:31:33.301Z" }
109
+ #
110
+ # # List DNS Records
111
+ # r53.list_resource_record_sets("/hostedzone/Z1K6NCF0EB26FB") #=>
112
+ # [{:type=>"NS",
113
+ # :ttl=>172800,
114
+ # :resource_records=>
115
+ # ["ns-1115.awsdns-11.org.",
116
+ # "ns-696.awsdns-23.net.",
117
+ # "ns-1963.awsdns-53.co.uk.",
118
+ # "ns-362.awsdns-45.com."],
119
+ # :name=>"my-awesome-site.com."},
120
+ # {:type=>"SOA",
121
+ # :ttl=>900,
122
+ # :resource_records=>
123
+ # ["ns-1115.awsdns-11.org. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400"],
124
+ # :name=>"my-awesome-site.com."},
125
+ # {:type=>"NS",
126
+ # :ttl=>600,
127
+ # :resource_records=>["www.mysite.com"],
128
+ # :name=>"www1.my-awesome-site.com."}]
129
+ #
130
+ # # Get Change info
131
+ # r53.get_change("/change/C2C6IGNRTKA0AY")
132
+ # {:status=>"INSYNC",
133
+ # :aws_id=>"/change/C2C6IGNRTKA0AY",
134
+ # :submitted_at=>"2011-01-19T08:29:26.160Z"}
135
+ #
136
+ # #------------------------
137
+ # # Delete Hosted Zone
138
+ # #------------------------
139
+ #
140
+ # # Get a list of DNS records I have created (the first 2 records were added by Amazon and cannot be deleted)
141
+ # resource_record_sets = r53.list_resource_record_sets("/hostedzone/Z1K6NCF0EB26FB")
142
+ # resource_record_sets.shift
143
+ # resource_record_sets.shift
144
+ #
145
+ # # Delete them all
146
+ # delete_resource_record_sets("/hostedzone/Z1K6NCF0EB26FB", resource_record_sets, 'kill all records I have created') #=>
147
+ # { :status=>"PENDING",
148
+ # :aws_id=>"/change/C6NCO8Z50MHXV",
149
+ # :submitted_at=>"2011-01-19T08:46:37.307Z" }
150
+ #
151
+ # # Delete Hosted Zone
152
+ # r53.delete_hosted_zone("/hostedzone/Z1K6NCF0EB26FB") #=>
153
+ # { :status=>"PENDING",
154
+ # :aws_id=>"/change/C3OJ31D4V5P2LU",
155
+ # :submitted_at=>"2011-01-19T08:46:37.530Z" }
156
+ #
157
+ class Route53Interface < RightAwsBase
158
+
159
+ include RightAwsBaseInterface
160
+
161
+ API_VERSION = "2011-05-05"
162
+ DEFAULT_HOST = "route53.amazonaws.com"
163
+ DEFAULT_PATH = '/'
164
+ DEFAULT_PROTOCOL = 'https'
165
+ DEFAULT_PORT = 443
166
+
167
+ @@bench = AwsBenchmarkingBlock.new
168
+ def self.bench_xml
169
+ @@bench.xml
170
+ end
171
+ def self.bench_service
172
+ @@bench.service
173
+ end
174
+
175
+ # Create a new handle to an Route53 account. All handles share the same per process or per thread
176
+ # HTTP connection to Amazon Route53. Each handle is for a specific account. The params have the
177
+ # following options:
178
+ # * <tt>:endpoint_url</tt> a fully qualified url to Amazon API endpoint (this overwrites: :server, :port, :service, :protocol).
179
+ # * <tt>:server</tt>: Route53 service host, default: DEFAULT_HOST
180
+ # * <tt>:port</tt>: Route53 service port, default: DEFAULT_PORT
181
+ # * <tt>:protocol</tt>: 'http' or 'https', default: DEFAULT_PROTOCOL
182
+ # * <tt>:logger</tt>: for log messages, default: RAILS_DEFAULT_LOGGER else STDOUT
183
+ # * <tt>:signature_version</tt>: The signature version : '0','1' or '2'(default)
184
+ #
185
+ def initialize(aws_access_key_id=nil, aws_secret_access_key=nil, params={})
186
+ init({ :name => 'ROUTE_53',
187
+ :default_host => ENV['ROUTE_53_URL'] ? URI.parse(ENV['ROUTE_53_URL']).host : DEFAULT_HOST,
188
+ :default_port => ENV['ROUTE_53_URL'] ? URI.parse(ENV['ROUTE_53_URL']).port : DEFAULT_PORT,
189
+ :default_service => ENV['ROUTE_53_URL'] ? URI.parse(ENV['ROUTE_53_URL']).path : DEFAULT_PATH,
190
+ :default_protocol => ENV['ROUTE_53_URL'] ? URI.parse(ENV['ROUTE_53_URL']).scheme : DEFAULT_PROTOCOL,
191
+ :default_api_version => ENV['ROUTE_53_API_VERSION'] || API_VERSION },
192
+ aws_access_key_id || ENV['AWS_ACCESS_KEY_ID'] ,
193
+ aws_secret_access_key|| ENV['AWS_SECRET_ACCESS_KEY'],
194
+ params)
195
+ end
196
+
197
+ #-----------------------------------------------------------------
198
+ # Requests
199
+ #-----------------------------------------------------------------
200
+
201
+ # Generates request hash for REST API.
202
+ def generate_request(method, path, params={}, body=nil, headers={}) # :nodoc:
203
+ # Params
204
+ params.delete_if{ |key, val| val.right_blank? }
205
+ unless params.right_blank?
206
+ path += "?" + params.to_a.collect{ |key,val| "#{AwsUtils::amz_escape(key)}=#{AwsUtils::amz_escape(val.to_s)}" }.join("&")
207
+ end
208
+ # Headers
209
+ headers = AwsUtils::fix_headers(headers)
210
+ headers['content-type'] ||= 'text/xml' if body
211
+ headers['date'] = Time.now.httpdate
212
+ # Auth
213
+ signature = AwsUtils::sign(@aws_secret_access_key, headers['date'])
214
+ headers['X-Amzn-Authorization'] = "AWS3-HTTPS AWSAccessKeyId=#{@aws_access_key_id},Algorithm=HmacSHA1,Signature=#{signature}"
215
+ # Request
216
+ path = "#{@params[:service]}#{@params[:api_version]}/#{path}"
217
+ request = "Net::HTTP::#{method.capitalize}".right_constantize.new(path)
218
+ request.body = body if body
219
+ # Set request headers
220
+ headers.each { |key, value| request[key.to_s] = value }
221
+ # prepare output hash
222
+ { :request => request,
223
+ :server => @params[:server],
224
+ :port => @params[:port],
225
+ :protocol => @params[:protocol] }
226
+ end
227
+
228
+ # Sends request to Amazon and parses the response.
229
+ # Raises AwsError if any banana happened.
230
+ def request_info(request, parser, &block) # :nodoc:
231
+ request_info_impl(:acf_connection, @@bench, request, parser, &block)
232
+ end
233
+
234
+ def incrementally_list_hosted_zones(path, parser, params={}, &block) # :nodoc:
235
+ opts = {}
236
+ opts['MaxItems'] = params[:max_items] if params[:max_items]
237
+ opts['Marker'] = params[:marker] if params[:marker]
238
+ last_response = nil
239
+ loop do
240
+ link = generate_request('GET', path, opts)
241
+ last_response = request_info(link, parser.new(:logger => @logger))
242
+ opts['Marker'] = last_response[:next_marker]
243
+ break unless block && block.call(last_response) && !last_response[:next_marker].right_blank?
244
+ end
245
+ last_response
246
+ end
247
+
248
+ def incrementally_list_resource_records(path, parser, params={}, &block) # :nodoc:
249
+ opts = {}
250
+ opts[:maxitems] = params.delete(:max_items) if params[:max_items]
251
+ last_response = nil
252
+ loop do
253
+ link = generate_request('GET', path, opts)
254
+ last_response = request_info(link, parser.new(:logger => @logger))
255
+ opts[:maxitems] = last_response[:max_items]
256
+ opts[:name] = last_response[:next_record_name]
257
+ opts[:type] = last_response[:next_record_type]
258
+ break unless block && block.call(last_response) && last_response[:is_truncated]
259
+ end
260
+ last_response
261
+ end
262
+
263
+ def expand_hosted_zone_id(aws_id) # :nodoc:
264
+ aws_id[%r{^/hostedzone/}] ? aws_id : "/hostedzone/#{aws_id}"
265
+ end
266
+
267
+ def expand_change_id(aws_id) # :nodoc:
268
+ aws_id[%r{^/change/}] ? aws_id : "/change/#{aws_id}"
269
+ end
270
+
271
+ def hosted_zone_config_to_xml(config) # :nodoc:
272
+ config[:caller_reference] ||= AwsUtils::generate_call_reference
273
+ hosted_zone_config = ''
274
+ unless config[:config].right_blank?
275
+ hosted_zone_config = " <HostedZoneConfig>\n" +
276
+ " <Comment>#{AwsUtils::xml_escape config[:config][:comment]}</Comment>\n" +
277
+ " </HostedZoneConfig>\n"
278
+ end
279
+ # XML
280
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
281
+ "<CreateHostedZoneRequest xmlns=\"https://#{@params[:server]}/doc/#{API_VERSION}/\">\n" +
282
+ " <Name>#{config[:name]}</Name>\n" +
283
+ " <CallerReference>#{config[:caller_reference]}</CallerReference>\n" +
284
+ hosted_zone_config +
285
+ "</CreateHostedZoneRequest>\n"
286
+ end
287
+
288
+ def resource_record_sets_to_xml(resource_record_changes, comment) # :nodoc:
289
+ # Comment
290
+ xml_comment = comment.right_blank? ? '' : " <Comment>#{AwsUtils::xml_escape(comment)}</Comment>\n"
291
+ # Changes
292
+ xml_changes = ''
293
+ resource_record_changes.each do |change|
294
+ xml_resource_records = Array(change[:resource_records]).map{|record| " <ResourceRecord><Value>#{AwsUtils::xml_escape(record)}</Value></ResourceRecord>\n" }.join('')
295
+ xml_changes += " <Change>\n" +
296
+ " <Action>#{AwsUtils::xml_escape(change[:action].to_s.upcase)}</Action>\n" +
297
+ " <ResourceRecordSet>\n" +
298
+ " <Name>#{AwsUtils::xml_escape(change[:name])}</Name>\n" +
299
+ " <Type>#{AwsUtils::xml_escape(change[:type].to_s.upcase)}</Type>\n"
300
+ if change[:alias_target]
301
+ alias_target = change[:alias_target]
302
+ xml_changes +=
303
+ " <AliasTarget>\n" +
304
+ " <HostedZoneId>#{AwsUtils::xml_escape(alias_target[:hosted_zone_id].to_s)}</HostedZoneId>\n" +
305
+ " <DNSName>#{AwsUtils::xml_escape(alias_target[:dns_name].to_s)}</DNSName>\n" +
306
+ " </AliasTarget>\n"
307
+ else
308
+ xml_changes +=
309
+ " <TTL>#{AwsUtils::xml_escape(change[:ttl].to_s)}</TTL>\n" +
310
+ " <ResourceRecords>\n" +
311
+ xml_resource_records +
312
+ " </ResourceRecords>\n"
313
+ end
314
+ xml_changes +=
315
+ " </ResourceRecordSet>\n" +
316
+ " </Change>\n"
317
+ end
318
+ # XML
319
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
320
+ "<ChangeResourceRecordSetsRequest xmlns=\"https://#{@params[:server]}/doc/#{API_VERSION}/\">\n" +
321
+ " <ChangeBatch>\n" +
322
+ xml_comment +
323
+ " <Changes>\n" +
324
+ xml_changes +
325
+ " </Changes>\n" +
326
+ " </ChangeBatch>\n" +
327
+ "</ChangeResourceRecordSetsRequest>\n"
328
+ end
329
+
330
+
331
+ #-----------------------------------------------------------------
332
+ # Hosted Zones
333
+ #-----------------------------------------------------------------
334
+
335
+ # List your hosted zones.
336
+ #
337
+ # r53.list_hosted_zones #=>
338
+ # [{:config=>{:comment=>"KD1, description"},
339
+ # :aws_id=>"/hostedzone/Z2P714ENJN23PN",
340
+ # :caller_reference=>"1295424990-710392-gqMuw-KcY8F-LFlrB-SQhp9",
341
+ # :name=>"patch-island.com."},
342
+ # {:config=>{:comment=>"My awesome site!"},
343
+ # :aws_id=>"/hostedzone/ZWEC7PPVACGQ4",
344
+ # :caller_reference=>"1295422234-657482-hfkeo-JFKid-Ldfle-Sdrty",
345
+ # :name=>"mysite.patch-island.com."}, ...]
346
+ #
347
+ # PS: http://docs.amazonwebservices.com/Route53/latest/APIReference/API_ListHostedZones.html
348
+ #
349
+ def list_hosted_zones
350
+ result = []
351
+ incrementally_list_hosted_zones('hostedzone', ListHostedZonesParser) do |response|
352
+ result += response[:items]
353
+ true
354
+ end
355
+ result
356
+ end
357
+
358
+ # Create new hosted zone
359
+ #
360
+ # config = {
361
+ # :name => 'mysite.patch-island.com.',
362
+ # :config => {
363
+ # :comment => 'My awesome site!'
364
+ # }
365
+ # }
366
+ # r53.create_hosted_zone(config) #=>
367
+ # {:config=>{:comment=>"My awesome site!"},
368
+ # :change_info=>
369
+ # {:status=>"PENDING",
370
+ # :aws_id=>"/change/C2NOTVGL7IOFFF",
371
+ # :submitted_at=>"2011-01-18T15:34:18.086Z"},
372
+ # :aws_id=>"/hostedzone/ZWEC7PPVACGQ4",
373
+ # :caller_reference=>"1295365357-227168-NfZ4P-VGHWi-Yq0p7-nuN6q",
374
+ # :name_servers=>
375
+ # ["ns-794.awsdns-35.net",
376
+ # "ns-459.awsdns-57.com",
377
+ # "ns-1537.awsdns-00.co.uk",
378
+ # "ns-1165.awsdns-17.org"],
379
+ # :name=>"mysite.patch-island.com."}
380
+ #
381
+ # PS: http://docs.amazonwebservices.com/Route53/latest/APIReference/index.html?API_CreateHostedZone.html
382
+ #
383
+ def create_hosted_zone(config)
384
+ config[:caller_reference] ||= AwsUtils::generate_unique_token
385
+ link = generate_request('POST', 'hostedzone', {}, hosted_zone_config_to_xml(config))
386
+ request_info(link, GetHostedZoneParser.new(:logger => @logger))
387
+ end
388
+
389
+ # Get your hosted zone.
390
+ #
391
+ # r53.get_hosted_zone("ZWEC7PPVACGQ4") #=>
392
+ # {:config=>{:comment=>"My awesome site!"},
393
+ # :aws_id=>"/hostedzone/ZWEC7PPVACGQ4",
394
+ # :caller_reference=>"1295422234-657482-hfkeo-JFKid-Ldfle-Sdrty",
395
+ # :name_servers=>
396
+ # ["ns-794.awsdns-35.net",
397
+ # "ns-459.awsdns-57.com",
398
+ # "ns-1537.awsdns-00.co.uk",
399
+ # "ns-1165.awsdns-17.org"],
400
+ # :name=>"mysite.patch-island.com."}
401
+ #
402
+ # PS: http://docs.amazonwebservices.com/Route53/latest/APIReference/API_GetHostedZone.html
403
+ #
404
+ def get_hosted_zone(hosted_zone_aws_id)
405
+ link = generate_request('GET', expand_hosted_zone_id(hosted_zone_aws_id))
406
+ request_info(link, GetHostedZoneParser.new(:logger => @logger))
407
+ end
408
+
409
+ # Delete hosted zone.
410
+ #
411
+ # r53.delete_hosted_zone("/hostedzone/Z2P714ENJN23PN") #=>
412
+ # {:status=>"PENDING",
413
+ # :submitted_at=>"2011-01-18T15:45:45.060Z",
414
+ # :aws_id=>"/change/C1PN1SDWZKPTAC"}
415
+ #
416
+ # PS: http://docs.amazonwebservices.com/Route53/latest/APIReference/API_DeleteHostedZone.html
417
+ #
418
+ def delete_hosted_zone(hosted_zone_aws_id)
419
+ link = generate_request('DELETE', expand_hosted_zone_id(hosted_zone_aws_id))
420
+ request_info(link, GetChangeParser.new(:logger => @logger))
421
+ end
422
+
423
+ #-----------------------------------------------------------------
424
+ # Resource Records Set
425
+ #-----------------------------------------------------------------
426
+
427
+ # List your resource record sets.
428
+ # Options: :type, :name, :max_items
429
+ #
430
+ # r53.list_resource_record_sets("/hostedzone/ZWEC7PPVACGQ4") #=>
431
+ # [{:type=>"NS",
432
+ # :ttl=>172800,
433
+ # :name=>"mysite.patch-island.com.",
434
+ # :resource_records=>
435
+ # ["ns-459.awsdns-57.com.",
436
+ # "ns-794.awsdns-35.net.",
437
+ # "ns-1165.awsdns-17.org.",
438
+ # "ns-1537.awsdns-00.co.uk."]},
439
+ # {:type=>"SOA",
440
+ # :ttl=>900,
441
+ # :name=>"mysite.patch-island.com.",
442
+ # :resource_records=>
443
+ # ["ns-794.awsdns-35.net. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400"]},
444
+ # {:type=>"NS",
445
+ # :ttl=>600,
446
+ # :resource_records=>["xxx.mysite.com"],
447
+ # :name=>"m1.mysite.patch-island.com."}]
448
+ #
449
+ # PS: http://docs.amazonwebservices.com/Route53/latest/APIReference/API_ListResourceRecordSets.html
450
+ #
451
+ def list_resource_record_sets(hosted_zone_aws_id, options={})
452
+ options = options.dup
453
+ result = []
454
+ incrementally_list_resource_records("#{expand_hosted_zone_id(hosted_zone_aws_id)}/rrset", ListResourceRecordSetsParser, options) do |response|
455
+ result += response[:items]
456
+ true
457
+ end
458
+ result
459
+ end
460
+
461
+ # Create or delete DNS records.
462
+ #
463
+ # resource_record_sets = [{ :action => :create,
464
+ # :name => 'm3.mysite.patch-island.com',
465
+ # :type => 'NS',
466
+ # :ttl => 600,
467
+ # :resource_records => 'xxx.mysite.com' },
468
+ # { :action => :delete,
469
+ # :name => 'm2.mysite.patch-island.com',
470
+ # :type => 'A',
471
+ # :ttl => 600,
472
+ # :resource_records => ['10.0.0.1'] }]
473
+ # r53.change_resource_record_sets("/hostedzone/Z1K6NCF0EB26FB", resource_record_sets, 'KD: Comment#1') #=>
474
+ # {:status=>"PENDING",
475
+ # :submitted_at=>"2011-01-18T20:21:56.828Z",
476
+ # :aws_id=>"/change/C394PNLM1B2P08"}
477
+ #
478
+ # PS: resource_record_sets must have an :action key set (== :create or :delete)
479
+ # PPS: http://docs.amazonwebservices.com/Route53/latest/APIReference/API_ChangeResourceRecordSets.html
480
+ #
481
+ def change_resource_record_sets(hosted_zone_aws_id, resource_record_sets, comment = '')
482
+ link = generate_request('POST', "#{expand_hosted_zone_id(hosted_zone_aws_id)}/rrset", {}, resource_record_sets_to_xml(resource_record_sets, comment))
483
+ request_info(link, GetChangeParser.new(:logger => @logger))
484
+ end
485
+
486
+ # Create DNS records.
487
+ #
488
+ # resource_record_sets = [{ :name => 'm3.mysite.patch-island.com',
489
+ # :type => 'NS',
490
+ # :ttl => 600,
491
+ # :resource_records => 'xxx.mysite.com' },
492
+ # { :name => 'm2.mysite.patch-island.com',
493
+ # :type => 'A',
494
+ # :ttl => 600,
495
+ # :resource_records => ['10.0.0.1'] }]
496
+ # r53.create_resource_record_sets("/hostedzone/Z1K6NCF0EB26FB", resource_record_sets, 'KD: Comment#1') #=>
497
+ # {:status=>"PENDING",
498
+ # :submitted_at=>"2011-01-18T20:21:56.828Z",
499
+ # :aws_id=>"/change/C394PNLM1B2P08"}
500
+ #
501
+ # PS: http://docs.amazonwebservices.com/Route53/latest/APIReference/API_ChangeResourceRecordSets.html
502
+ #
503
+ def create_resource_record_sets(hosted_zone_aws_id, resource_record_sets, comment = '')
504
+ resource_record_sets.each{|rrs| rrs[:action] = :create}
505
+ change_resource_record_sets(hosted_zone_aws_id, resource_record_sets, comment)
506
+ end
507
+
508
+ # Delete DNS records.
509
+ #
510
+ # resource_record_sets = [{ :name => 'm3.mysite.patch-island.com',
511
+ # :type => 'NS',
512
+ # :ttl => 600,
513
+ # :resource_records => 'xxx.mysite.com' },
514
+ # { :name => 'm2.mysite.patch-island.com',
515
+ # :type => 'A',
516
+ # :ttl => 600,
517
+ # :resource_records => ['10.0.0.1'] }]
518
+ # r53.create_resource_record_sets("/hostedzone/Z1K6NCF0EB26FB", resource_record_sets, 'KD: Comment#1') #=>
519
+ # {:status=>"PENDING",
520
+ # :submitted_at=>"2011-01-18T20:21:56.828Z",
521
+ # :aws_id=>"/change/C394PNLM1B2P08"}
522
+ #
523
+ # PS: http://docs.amazonwebservices.com/Route53/latest/APIReference/API_ChangeResourceRecordSets.html
524
+ #
525
+ def delete_resource_record_sets(hosted_zone_aws_id, resource_record_sets, comment = '')
526
+ resource_record_sets.each{|rrs| rrs[:action] = :delete}
527
+ change_resource_record_sets(hosted_zone_aws_id, resource_record_sets, comment)
528
+ end
529
+
530
+
531
+ # Get the current state of a change request.
532
+ #
533
+ # r53.get_change("/change/C1PN1SDWZKPTAC") #=>
534
+ # {:status=>"INSYNC",
535
+ # :aws_id=>"/change/C1PN1SDWZKPTAC",
536
+ # :submitted_at=>"2011-01-18T15:45:45.060Z"}
537
+ #
538
+ # PS: http://docs.amazonwebservices.com/Route53/latest/APIReference/API_GetChange.html
539
+ #
540
+ def get_change(change_aws_id)
541
+ link = generate_request('GET', expand_change_id(change_aws_id))
542
+ request_info(link, GetChangeParser.new(:logger => @logger))
543
+ end
544
+
545
+ #-----------------------------------------------------------------
546
+ # Hosted Zones
547
+ #-----------------------------------------------------------------
548
+
549
+ class ListHostedZonesParser < RightAWSParser # :nodoc:
550
+ def reset
551
+ @result = { :items => [] }
552
+ end
553
+ def tagstart(name, attributes)
554
+ case name
555
+ when 'HostedZone' then @item = { :config => {} }
556
+ end
557
+ end
558
+ def tagend(name)
559
+ case name
560
+ when 'IsTruncated' then @result[:is_truncated] = @text == 'true'
561
+ when 'NextMarker' then @result[:next_marker] = @text
562
+ when 'MaxItems' then @result[:max_items] = @text.to_i
563
+ when 'Id' then @item[:aws_id] = @text
564
+ when 'Name' then @item[:name] = @text
565
+ when 'CallerReference' then @item[:caller_reference] = @text
566
+ when 'HostedZone' then @result[:items] << @item
567
+ else
568
+ case full_tag_name
569
+ when %r{/Config/Comment$} then @item[:config][:comment] = @text
570
+ end
571
+ end
572
+ end
573
+ end
574
+
575
+ class GetHostedZoneParser < RightAWSParser # :nodoc:
576
+ def reset
577
+ @result = {}
578
+ end
579
+ def tagend(name)
580
+ case full_tag_name
581
+ when %r{/HostedZone/Id} then @result[:aws_id] = @text
582
+ when %r{/HostedZone/Name} then @result[:name] = @text
583
+ when %r{/HostedZone/CallerReference} then @result[:caller_reference] = @text
584
+ when %r{/Config/Comment$} then (@result[:config] ||= {})[:comment] = AwsUtils::xml_unescape(@text)
585
+ when %r{/ChangeInfo/Id$} then (@result[:change_info] ||= {})[:aws_id] = @text
586
+ when %r{/ChangeInfo/Status$} then (@result[:change_info] ||= {})[:status] = @text
587
+ when %r{/ChangeInfo/SubmittedAt$} then (@result[:change_info] ||= {})[:submitted_at] = @text
588
+ when %r{/DelegationSet/NameServers/NameServer$} then (@result[:name_servers] ||= []) << @text
589
+ end
590
+ end
591
+ end
592
+
593
+ #-----------------------------------------------------------------
594
+ # Resource Records Set
595
+ #-----------------------------------------------------------------
596
+
597
+ class ListResourceRecordSetsParser < RightAWSParser # :nodoc:
598
+ def reset
599
+ @result = { :items => [] }
600
+ end
601
+ def tagstart(name, attributes)
602
+ case name
603
+ when 'ResourceRecordSet' then @item = {}
604
+ end
605
+ end
606
+ def tagend(name)
607
+ case name
608
+ when 'IsTruncated' then @result[:is_truncated] = @text == 'true'
609
+ when 'NextRecordName' then @result[:next_record_name] = @text
610
+ when 'NextRecordType' then @result[:next_record_type] = @text
611
+ when 'MaxItems' then @result[:max_items] = @text.to_i
612
+ when 'Type' then @item[:type] = @text
613
+ when 'Name' then @item[:name] = @text
614
+ when 'TTL' then @item[:ttl] = @text.to_i
615
+ when 'ResourceRecordSet' then @result[:items] << @item
616
+ else
617
+ case full_tag_name
618
+ when %r{/ResourceRecord/Value} then (@item[:resource_records] ||= []) << @text
619
+ when %r{/AliasTarget/DNSName} then (@item[:alias_target] ||= {})[:dns_name] = @text
620
+ when %r{/AliasTarget/HostedZoneId} then (@item[:alias_target] ||= {})[:hosted_zone_id] = @text
621
+ end
622
+ end
623
+ end
624
+ end
625
+
626
+ class GetChangeParser < RightAWSParser # :nodoc:
627
+ def reset
628
+ @result = { }
629
+ end
630
+ def tagend(name)
631
+ case name
632
+ when 'Id' then @result[:aws_id] = @text
633
+ when 'Status' then @result[:status] = @text
634
+ when 'SubmittedAt' then @result[:submitted_at] = @text
635
+ end
636
+ end
637
+ end
638
+
639
+ end
640
+
641
+ end