route53 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7245a232925c75451ca48b244e29e5050e201eb3
4
+ data.tar.gz: fcc78bc8b07928023c71c6f5c1e86180be9e70dd
5
+ SHA512:
6
+ metadata.gz: 4aac19d233d5032337712448af778c6808188d69b7db542e8d4ae76dea6d4121fef4bb361827022ce0759fdf4d4364fbf55cd63d68d8448e0e6749a1b755059a
7
+ data.tar.gz: 2f1390fa5be1861a4418ea332e1f208ab2ebfa385e7cca404c89c8e90c81d0214129c0c6c5d21c04afe80ef7817e163560586ecb4f63f57b17245cd0533fca6a
data/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  pkg
2
2
  route53-*.gem
3
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
@@ -0,0 +1 @@
1
+ route53
@@ -0,0 +1 @@
1
+ ruby-2.0.0-p353
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.8.7
5
+ - 2.1.0
6
+ - 2.0.0
7
+ - ree
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,5 @@
1
+ require 'rspec/core/rake_task'
2
+
3
+ RSpec::Core::RakeTask.new(:spec)
4
+
5
+ task :default => :spec
@@ -1,4 +1,3 @@
1
-
2
1
  require 'rubygems'
3
2
  require 'hmac'
4
3
  require 'hmac-sha2'
@@ -7,418 +6,13 @@ require 'time'
7
6
  require 'net/http'
8
7
  require 'net/https'
9
8
  require 'uri'
10
- require 'hpricot'
9
+ require 'nokogiri'
11
10
  require 'builder'
12
11
  require 'digest/md5'
12
+ require 'route53/connection'
13
+ require 'route53/zone'
14
+ require 'route53/aws_response'
15
+ require 'route53/dns_record'
13
16
 
14
17
  module Route53
15
-
16
- class Connection
17
- attr_reader :base_url
18
- attr_reader :api
19
- attr_reader :endpoint
20
- attr_reader :verbose
21
-
22
- def initialize(accesskey,secret,api='2012-12-12',endpoint='https://route53.amazonaws.com/',verbose=false,ssl_no_verify=false)
23
- @accesskey = accesskey
24
- @secret = secret
25
- @api = api
26
- @endpoint = endpoint
27
- @base_url = endpoint+@api
28
- @verbose = verbose
29
- @ssl_no_verify = ssl_no_verify
30
- end
31
-
32
- def request(url,type = "GET",data = nil)
33
- puts "URL: #{url}" if @verbose
34
- puts "Type: #{type}" if @verbose
35
- puts "Req: #{data}" if type != "GET" && @verbose
36
- uri = URI(url)
37
- http = Net::HTTP.new(uri.host, uri.port)
38
- http.use_ssl = true if uri.scheme == "https"
39
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if RUBY_VERSION.start_with?("1.8") or @ssl_no_verify
40
- time = get_date
41
- hmac = HMAC::SHA256.new(@secret)
42
- hmac.update(time)
43
- signature = Base64.encode64(hmac.digest).chomp
44
- headers = {
45
- 'Date' => time,
46
- 'X-Amzn-Authorization' => "AWS3-HTTPS AWSAccessKeyId=#{@accesskey},Algorithm=HmacSHA256,Signature=#{signature}",
47
- 'Content-Type' => 'text/xml; charset=UTF-8'
48
- }
49
- resp = http.send_request(type,uri.path+"?"+(uri.query.nil? ? "" : uri.query),data,headers)
50
- #puts "Resp:"+resp.to_s if @verbose
51
- #puts "RespBody: #{resp.body}" if @verbose
52
- return AWSResponse.new(resp.body,self)
53
- end
54
-
55
- def get_zones(name = nil)
56
- truncated = true
57
- query = []
58
- zones = []
59
- while truncated
60
- if !name.nil? && name.start_with?("/hostedzone/")
61
- resp = request("#{@base_url}#{name}")
62
- truncated = false
63
- else
64
- resp = request("#{@base_url}/hostedzone?"+query.join("&"))
65
- end
66
- return nil if resp.error?
67
- zone_list = Hpricot::XML(resp.raw_data)
68
- elements = zone_list.search("HostedZone")
69
- elements.each do |e|
70
- zones.push(Zone.new(e.search("Name").first.innerText,
71
- e.search("Id").first.innerText,
72
- self))
73
- end
74
- truncated = (zone_list.search("IsTruncated").first.innerText == "true") if truncated
75
- query = ["marker="+zone_list.search("NextMarker").first.innerText] if truncated
76
- end
77
- unless name.nil? || name.start_with?("/hostedzone/")
78
- name_arr = name.split('.')
79
- (0 ... name_arr.size).each do |i|
80
- search_domain = name_arr.last(name_arr.size-i).join('.')+"."
81
- zone_select = zones.select { |z| z.name == search_domain }
82
- return zone_select
83
- end
84
- return nil
85
- end
86
- return zones
87
- end
88
-
89
- def get_date
90
- #return Time.now.utc.rfc2822
91
- #Cache date for 30 seconds to reduce extra calls
92
- if @date_stale.nil? || @date_stale < Time.now - 30
93
- uri = URI(@endpoint)
94
- http = Net::HTTP.new(uri.host, uri.port)
95
- http.use_ssl = true if uri.scheme == "https"
96
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE if RUBY_VERSION.start_with?("1.8") or @ssl_no_verify
97
- resp = nil
98
- puts "Making Date Request" if @verbose
99
- http.start { |http| resp = http.head('/date') }
100
- @date = resp['Date']
101
- @date_stale = Time.now
102
- puts "Received Date." if @verbose
103
- end
104
- return @date
105
- end
106
-
107
- end
108
-
109
-
110
- class Zone
111
- attr_reader :host_url
112
- attr_reader :name
113
- attr_reader :records
114
- attr_reader :conn
115
-
116
- def initialize(name,host_url,conn)
117
- @name = name
118
- unless @name.end_with?(".")
119
- @name += "."
120
- end
121
- @host_url = host_url
122
- @conn = conn
123
- end
124
-
125
- def nameservers
126
- return @nameservers if @nameservers
127
- response = Hpricot::XML(@conn.request(@conn.base_url + @host_url).to_s)
128
- @nameservers = response.search("NameServer").map(&:innerText)
129
- @nameservers
130
- end
131
-
132
- def delete_zone
133
- @conn.request(@conn.base_url + @host_url,"DELETE")
134
- end
135
-
136
- def create_zone(comment = nil)
137
- xml_str = ""
138
- xml = Builder::XmlMarkup.new(:target=>xml_str, :indent=>2)
139
- xml.instruct!
140
- xml.CreateHostedZoneRequest(:xmlns => @conn.endpoint+'doc/'+@conn.api+'/') { |create|
141
- create.Name(@name)
142
- # AWS lists this as required
143
- # "unique string that identifies the request and that
144
- # allows failed CreateHostedZone requests to be retried without the risk of executing the operation twice."
145
- # Just going to pass a random string instead.
146
- create.CallerReference(rand(2**32).to_s(16))
147
- create.HostedZoneConfig { |conf|
148
- conf.Comment(comment)
149
- }
150
- }
151
- #puts "XML:\n#{xml_str}" if @conn.verbose
152
- resp = @conn.request(@conn.base_url + "/hostedzone","POST",xml_str)
153
- resp_xml = Hpricot::XML(resp.raw_data)
154
- @host_url = resp_xml.search("HostedZone").first.search("Id").first.innerText
155
- return resp
156
- end
157
-
158
- def get_records(type="ANY")
159
- return nil if host_url.nil?
160
-
161
- truncated = true
162
- query = []
163
- dom_records = []
164
- while truncated
165
- resp = @conn.request(@conn.base_url+@host_url+"/rrset?"+query.join("&"))
166
- if resp.error?
167
- return nil
168
- end
169
- zone_file = Hpricot::XML(resp.raw_data)
170
- records = zone_file.search("ResourceRecordSet")
171
-
172
- records.each do |record|
173
- #puts "Name:"+record.search("Name").first.innerText if @conn.verbose
174
- #puts "Type:"+record.search("Type").first.innerText if @conn.verbose
175
- #puts "TTL:"+record.search("TTL").first.innerText if @conn.verbose
176
- #record.search("Value").each do |val|
177
- # #puts "Val:"+val.innerText if @conn.verbose
178
- #end
179
- zone_apex_records = record.search("HostedZoneId")
180
- values = record.search("Value").map { |val| val.innerText }
181
- values << record.search("DNSName").first.innerText unless zone_apex_records.empty?
182
- weight_records = record.search("Weight")
183
- ident_records = record.search("SetIdentifier")
184
- dom_records.push(DNSRecord.new(record.search("Name").first.innerText,
185
- record.search("Type").first.innerText,
186
- (record.search("TTL").first.innerText if zone_apex_records.empty?),
187
- values,
188
- self,
189
- (zone_apex_records.first.innerText unless zone_apex_records.empty?),
190
- (weight_records.first.innerText unless weight_records.empty?),
191
- (ident_records.first.innerText unless ident_records.empty?)
192
- ))
193
- end
194
-
195
- truncated = (zone_file.search("IsTruncated").first.innerText == "true")
196
- if truncated
197
- next_name = zone_file.search("NextRecordName").first.innerText
198
- next_type = zone_file.search("NextRecordType").first.innerText
199
- query = ["name="+next_name,"type="+next_type]
200
- end
201
- end
202
- @records = dom_records
203
- if type != 'ANY'
204
- return dom_records.select { |r| r.type == type }
205
- end
206
- return dom_records
207
- end
208
-
209
- #When deleting a record an optional value is available to specify just a single value within a recordset like an MX record
210
- #Takes an array of [:action => , :record => ] where action is either CREATE or DELETE and record is a DNSRecord
211
- def gen_change_xml(change_list,comment=nil)
212
- #Get zone list and pick zone that matches most ending chars
213
-
214
- xml_str = ""
215
- xml = Builder::XmlMarkup.new(:target=>xml_str, :indent=>2)
216
- xml.instruct!
217
- xml.ChangeResourceRecordSetsRequest(:xmlns => @conn.endpoint+'doc/'+@conn.api+'/') { |req|
218
- req.ChangeBatch { |batch|
219
- batch.Comment(comment) unless comment.nil?
220
- batch.Changes { |changes|
221
- change_list.each { |change_item|
222
- change_item[:record].gen_change_xml(changes,change_item[:action])
223
- }
224
- }
225
- }
226
- }
227
- #puts "XML:\n#{xml_str}" if @conn.verbose
228
- return xml_str
229
- end
230
-
231
- #For modifying multiple or single records within a single transaction
232
- def perform_actions(change_list,comment=nil)
233
- xml_str = gen_change_xml(change_list,comment)
234
- @conn.request(@conn.base_url + @host_url+"/rrset","POST",xml_str)
235
- end
236
-
237
-
238
- def to_s
239
- return "#{@name} #{@host_url}"
240
- end
241
- end
242
-
243
- class AWSResponse
244
- attr_reader :raw_data
245
-
246
- #I wanted to put this in a seprate file but ruby's method of determinign the root of the gem is a pain in the butt and I was in a hurry. Sorry. -PC
247
-
248
-
249
- def initialize(resp,conn)
250
- @raw_data = unescape(resp)
251
- if error?
252
- $stderr.puts "ERROR: Amazon returned an error for the request."
253
- $stderr.puts "ERROR: RAW_XML: "+@raw_data
254
- $stderr.puts "ERROR: "+error_message
255
- $stderr.puts ""
256
- $stderr.puts "What now? "+helpful_message
257
- #exit 1
258
- end
259
- @conn = conn
260
- @created = Time.now
261
- puts "Raw: #{@raw_data}" if @conn.verbose
262
- end
263
-
264
- def error?
265
- return Hpricot::XML(@raw_data).search("ErrorResponse").size > 0
266
- end
267
-
268
- def error_message
269
- xml = Hpricot::XML(@raw_data)
270
- msg_code = xml.search("Code")
271
- msg_text = xml.search("Message")
272
- return (msg_code.size > 0 ? msg_code.first.inner_text : "") + (msg_text.size > 0 ? ': ' + msg_text.first.innerText : "")
273
- end
274
-
275
- def helpful_message
276
- xml = Hpricot::XML(@raw_data)
277
- msg_code = xml.search("Code").first.innerText
278
- return $messages[msg_code] if $messages[msg_code]
279
- return $messages["Other"]
280
- end
281
-
282
- def complete?
283
- return true if error?
284
- if @change_url.nil?
285
- change = Hpricot::XML(@raw_data).search("ChangeInfo")
286
- if change.size > 0
287
- @change_url = change.first.search("Id").first.innerText
288
- else
289
- return false
290
- end
291
- end
292
- if @complete.nil? || @complete == false
293
- status = Hpricot::XML(@conn.request(@conn.base_url+@change_url).raw_data).search("Status")
294
- @complete = status.size > 0 && status.first.innerText == "INSYNC" ? true : false
295
- if !@complete && @created - Time.now > 60
296
- $stderr.puts "WARNING: Amazon Route53 Change timed out on Sync. This may not be an issue as it may just be Amazon being assy. Then again your request may not have completed.'"
297
- @complete = true
298
- end
299
- end
300
- return @complete
301
- end
302
-
303
- def pending?
304
- #Return opposite of complete via XOR
305
- return complete? ^ true
306
- end
307
-
308
- def to_s
309
- return @raw_data
310
- end
311
-
312
- def unescape(string)
313
- string.gsub(/\\0(\d{2})/) { $1.oct.chr }
314
- end
315
- end
316
-
317
- class DNSRecord
318
- attr_reader :name
319
- attr_reader :type
320
- attr_reader :ttl
321
- attr_reader :values
322
- attr_reader :weight
323
- attr_reader :ident
324
- attr_reader :zone_apex
325
-
326
- def initialize(name,type,ttl,values,zone,zone_apex=nil,weight=nil,ident=nil)
327
- @name = name
328
- unless @name.end_with?(".")
329
- @name += "."
330
- end
331
- @type = type.upcase
332
- @ttl = ttl
333
- @values = values
334
- @zone = zone
335
- @zone_apex = zone_apex
336
- @weight = weight
337
- @ident = ident
338
- end
339
-
340
- def gen_change_xml(xml,action)
341
- xml.Change { |change|
342
- change.Action(action.upcase)
343
- change.ResourceRecordSet { |record|
344
- record.Name(@name)
345
- record.Type(@type)
346
- record.SetIdentifier(@ident) if @ident
347
- record.Weight(@weight) if @weight
348
- record.TTL(@ttl) unless @zone_apex
349
- if @zone_apex
350
- record.AliasTarget { |targets|
351
- targets.HostedZoneId(@zone_apex)
352
- targets.DNSName(@values.first)
353
- }
354
- else
355
- record.ResourceRecords { |resources|
356
- @values.each { |val|
357
- resources.ResourceRecord { |record|
358
- record.Value(val)
359
- }
360
- }
361
- }
362
- end
363
- }
364
- }
365
- end
366
-
367
- def delete(comment=nil)
368
- @zone.perform_actions([{:action => "DELETE", :record => self}],comment)
369
- end
370
-
371
- def create(comment=nil)
372
- @zone.perform_actions([{:action => "CREATE", :record => self}],comment)
373
- end
374
-
375
- #Need to modify to a param hash
376
- def update(name,type,ttl,values,comment=nil, zone_apex = nil)
377
- prev = self.clone
378
- @name = name unless name.nil?
379
- @type = type unless type.nil?
380
- @ttl = ttl unless ttl.nil?
381
- @values = values unless values.nil?
382
- @zone_apex = zone_apex unless zone_apex.nil?
383
- @zone.perform_actions([
384
- {:action => "DELETE", :record => prev},
385
- {:action => "CREATE", :record => self},
386
- ],comment)
387
- end
388
-
389
- #Returns the raw array so the developer can update large batches manually
390
- #Need to modify to a param hash
391
- def update_dirty(name,type,ttl,values,zone_apex = nil)
392
- prev = self.clone
393
- @name = name unless name.nil?
394
- @type = type unless type.nil?
395
- @ttl = ttl unless ttl.nil?
396
- @values = values unless values.nil?
397
- @zone_apex = zone_apex unless zone_apex.nil?
398
- return [{:action => "DELETE", :record => prev},
399
- {:action => "CREATE", :record => self}]
400
- end
401
-
402
- def to_s
403
- if @weight
404
- "#{@name} #{@type} #{@ttl} '#{@ident}' #{@weight} #{@values.join(",")}"
405
- elsif @zone_apex
406
- "#{@name} #{@type} #{@zone_apex} #{@values.join(",")}"
407
- else
408
- "#{@name} #{@type} #{@ttl} #{@values.join(",")}"
409
- end
410
- end
411
- end
412
-
413
18
  end
414
-
415
- $messages = { "InvalidClientTokenId" => "You may have a missing or incorrect secret or access key. Please double check your configuration files and amazon account",
416
- "MissingAuthenticationToken" => "You may have a missing or incorrect secret or access key. Please double check your configuration files and amazon account",
417
- "OptInRequired" => "In order to use Amazon's Route 53 service you first need to signup for it. Please see http://aws.amazon.com/route53/ for your account information and use the associated access key and secret.",
418
- "Other" => "It looks like you've run into an unhandled error. Please send a detailed bug report with the entire input and output from the program to support@50projects.com or to https://github.com/pcorliss/ruby_route_53/issues and we'll do out best to help you.",
419
- "SignatureDoesNotMatch" => "It looks like your secret key is incorrect or no longer valid. Please check your amazon account information for the proper key.",
420
- "HostedZoneNotEmpty" => "You'll need to first delete the contents of this zone. You can do so using the '--remove' option as part of the command line interface.",
421
- "InvalidChangeBatch" => "You may have tried to delete a NS or SOA record. This error is safe to ignore if you're just trying to delete all records as part of a zone prior to deleting the zone. Or you may have tried to create a record that already exists. Otherwise please file a bug by sending a detailed bug report with the entire input and output from the program to support@50projects.com or to https://github.com/pcorliss/ruby_route_53/issues and we'll do out best to help you.",
422
- "ValidationError" => "Check over your input again to make sure the record to be created is valid. The error message should give you some hints on what went wrong. If you're still having problems please file a bug by sending a detailed bug report with the entire input and output from the program to support@50projects.com or to https://github.com/pcorliss/ruby_route_53/issues and we'll do out best to help you."}
423
-
424
-