route53 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +21 -0
- data/README.markdown +0 -0
- data/Rakefile +2 -0
- data/lib/route53.rb +301 -0
- data/lib/route53/version.rb +3 -0
- data/route53.gemspec +24 -0
- metadata +126 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
pkg
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
route53 (0.1.0)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: http://rubygems.org/
|
8
|
+
specs:
|
9
|
+
builder (3.0.0)
|
10
|
+
hpricot (0.8.3)
|
11
|
+
ruby-hmac (0.4.0)
|
12
|
+
|
13
|
+
PLATFORMS
|
14
|
+
ruby
|
15
|
+
|
16
|
+
DEPENDENCIES
|
17
|
+
builder
|
18
|
+
bundler (>= 1.0.0)
|
19
|
+
hpricot
|
20
|
+
route53!
|
21
|
+
ruby-hmac
|
data/README.markdown
ADDED
File without changes
|
data/Rakefile
ADDED
data/lib/route53.rb
ADDED
@@ -0,0 +1,301 @@
|
|
1
|
+
module Route53
|
2
|
+
require 'hmac'
|
3
|
+
require 'hmac-sha2'
|
4
|
+
require 'base64'
|
5
|
+
require 'time'
|
6
|
+
require 'net/http'
|
7
|
+
require 'uri'
|
8
|
+
require 'hpricot'
|
9
|
+
require 'builder'
|
10
|
+
require 'digest/md5'
|
11
|
+
|
12
|
+
class Connection
|
13
|
+
attr_reader :base_url
|
14
|
+
attr_reader :api
|
15
|
+
attr_reader :endpoint
|
16
|
+
|
17
|
+
def initialize(accesskey,secret,api='2010-10-01',endpoint='https://route53.amazonaws.com/')
|
18
|
+
@accesskey = accesskey
|
19
|
+
@secret = secret
|
20
|
+
@api = api
|
21
|
+
@endpoint = endpoint
|
22
|
+
@base_url = endpoint+@api
|
23
|
+
end
|
24
|
+
|
25
|
+
def request(url,type = "GET",data = nil)
|
26
|
+
puts "URL: #{url}"
|
27
|
+
uri = URI(url)
|
28
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
29
|
+
http.use_ssl = true
|
30
|
+
time = get_date
|
31
|
+
hmac = HMAC::SHA256.new(@secret)
|
32
|
+
hmac.update(time)
|
33
|
+
signature = Base64.encode64(hmac.digest).chomp
|
34
|
+
headers = {
|
35
|
+
'Date' => time,
|
36
|
+
'X-Amzn-Authorization' => "AWS3-HTTPS AWSAccessKeyId=#{@accesskey},Algorithm=HmacSHA256,Signature=#{signature}",
|
37
|
+
'Content-Type' => 'text/xml; charset=UTF-8'
|
38
|
+
}
|
39
|
+
resp, data = http.send_request(type,uri.path,data,headers)
|
40
|
+
puts "Resp:"+resp.to_s
|
41
|
+
#puts "Data:"+data
|
42
|
+
return AWSResponse.new(data,self)
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_zones(name = nil)
|
46
|
+
resp = request("#{@base_url}/hostedzone")
|
47
|
+
if resp.error?
|
48
|
+
return nil
|
49
|
+
end
|
50
|
+
zone_list = Hpricot::XML(resp.raw_data)
|
51
|
+
zones = []
|
52
|
+
elements = zone_list.search("HostedZone")
|
53
|
+
elements.each do |e|
|
54
|
+
zones.push(Zone.new(e.search("Name").first.innerText,
|
55
|
+
e.search("Id").first.innerText,
|
56
|
+
self))
|
57
|
+
end
|
58
|
+
unless name.nil?
|
59
|
+
name_arr = name.split('.')
|
60
|
+
(0 ... name_arr.size).each do |i|
|
61
|
+
search_domain = name_arr.last(name_arr.size-i).join('.')+"."
|
62
|
+
zone_select = zones.select { |z| z.name == search_domain }
|
63
|
+
return zone_select if zone_select.size == 1
|
64
|
+
end
|
65
|
+
return nil
|
66
|
+
end
|
67
|
+
return zones
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_date
|
71
|
+
#return Time.now.utc.rfc2822
|
72
|
+
uri = URI(@endpoint)
|
73
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
74
|
+
http.use_ssl = true
|
75
|
+
resp = nil
|
76
|
+
http.start { |http| resp = http.head('/date') }
|
77
|
+
return resp['Date']
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
class Zone
|
84
|
+
attr_reader :host_url
|
85
|
+
attr_reader :name
|
86
|
+
attr_reader :records
|
87
|
+
attr_reader :conn
|
88
|
+
|
89
|
+
def initialize(name,host_url,conn)
|
90
|
+
@name = name
|
91
|
+
unless @name.end_with?(".")
|
92
|
+
@name += "."
|
93
|
+
end
|
94
|
+
@host_url = host_url
|
95
|
+
@conn = conn
|
96
|
+
end
|
97
|
+
|
98
|
+
def exists?
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
def delete_zone
|
103
|
+
@conn.request(@conn.base_url + @host_url,"DELETE")
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_zone(comment = nil)
|
107
|
+
xml_str = ""
|
108
|
+
xml = Builder::XmlMarkup.new(:target=>xml_str, :indent=>2)
|
109
|
+
xml.instruct!
|
110
|
+
xml.CreateHostedZoneRequest(:xmlns => @endpoint+'doc/'+@api+'/') { |create|
|
111
|
+
create.Name(@name)
|
112
|
+
# AWS lists this as required
|
113
|
+
# "unique string that identifies the request and that
|
114
|
+
# allows failed CreateHostedZone requests to be retried without the risk of executing the operation twice."
|
115
|
+
# Just going to pass a random string instead.
|
116
|
+
create.CallerReference(rand().to_s)
|
117
|
+
create.HostedZoneConfig { |conf|
|
118
|
+
conf.Comment(comment)
|
119
|
+
}
|
120
|
+
}
|
121
|
+
puts "XML:\n#{xml_str}"
|
122
|
+
@conn.request(@conn.base_url + "/hostedzone","POST",xml_str)
|
123
|
+
end
|
124
|
+
|
125
|
+
def get_records(type="ANY")
|
126
|
+
return nil if host_url.nil?
|
127
|
+
resp = @conn.request(@conn.base_url+@host_url+"/rrset")
|
128
|
+
if resp.error?
|
129
|
+
return nil
|
130
|
+
end
|
131
|
+
zone_file = Hpricot::XML(resp.raw_data)
|
132
|
+
records = zone_file.search("ResourceRecordSet")
|
133
|
+
|
134
|
+
dom_records = []
|
135
|
+
records.each do |record|
|
136
|
+
puts "Name:"+record.search("Name").first.innerText
|
137
|
+
puts "Type:"+record.search("Type").first.innerText
|
138
|
+
puts "TTL:"+record.search("TTL").first.innerText
|
139
|
+
record.search("Value").each do |val|
|
140
|
+
puts "Val:"+val.innerText
|
141
|
+
end
|
142
|
+
dom_records.push(DNSRecord.new(record.search("Name").first.innerText,
|
143
|
+
record.search("Type").first.innerText,
|
144
|
+
record.search("TTL").first.innerText,
|
145
|
+
record.search("Value").map { |val| val.innerText },
|
146
|
+
self))
|
147
|
+
end
|
148
|
+
@records = dom_records
|
149
|
+
if type != 'ANY'
|
150
|
+
return dom_records.select { |r| r.type == type }
|
151
|
+
end
|
152
|
+
return dom_records
|
153
|
+
end
|
154
|
+
|
155
|
+
#When deleting a record an optional value is available to specify just a single value within a recordset like an MX record
|
156
|
+
#Takes an array of [:action => , :record => ] where action is either CREATE or DELETE and record is a DNSRecord
|
157
|
+
def gen_change_xml(change_list,comment=nil)
|
158
|
+
#Get zone list and pick zone that matches most ending chars
|
159
|
+
|
160
|
+
xml_str = ""
|
161
|
+
xml = Builder::XmlMarkup.new(:target=>xml_str, :indent=>2)
|
162
|
+
xml.instruct!
|
163
|
+
xml.ChangeResourceRecordSetsRequest(:xmlns => @conn.endpoint+'doc/'+@conn.api+'/') { |req|
|
164
|
+
req.ChangeBatch { |batch|
|
165
|
+
batch.Comment(comment) unless comment.nil?
|
166
|
+
batch.Changes { |changes|
|
167
|
+
change_list.each { |change_item|
|
168
|
+
change_item[:record].gen_change_xml(changes,change_item[:action])
|
169
|
+
}
|
170
|
+
}
|
171
|
+
}
|
172
|
+
}
|
173
|
+
puts "XML:\n#{xml_str}"
|
174
|
+
return xml_str
|
175
|
+
end
|
176
|
+
|
177
|
+
#For modifying multiple or single records within a single transaction
|
178
|
+
def perform_actions(change_list,comment=nil)
|
179
|
+
xml_str = gen_change_xml(change_list,comment)
|
180
|
+
@conn.request(@conn.base_url + @host_url+"/rrset","POST",xml_str)
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
def to_s
|
185
|
+
return "#{@name} #{@host_url}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
class AWSResponse
|
190
|
+
attr_reader :raw_data
|
191
|
+
def initialize(resp,conn)
|
192
|
+
@raw_data = resp
|
193
|
+
if error?
|
194
|
+
$stderr.puts "An Error has occured"
|
195
|
+
$stderr.puts @raw_data
|
196
|
+
end
|
197
|
+
@conn = conn
|
198
|
+
end
|
199
|
+
|
200
|
+
def error?
|
201
|
+
return Hpricot::XML(@raw_data).search("ErrorResponse").size > 0
|
202
|
+
end
|
203
|
+
|
204
|
+
def complete?
|
205
|
+
if @change_url.nil?
|
206
|
+
change = Hpricot::XML(@raw_data).search("ChangeInfo")
|
207
|
+
if change.size > 0
|
208
|
+
@change_url = change.first.search("Id").first.innerText
|
209
|
+
else
|
210
|
+
return false
|
211
|
+
end
|
212
|
+
end
|
213
|
+
if @complete.nil? || @complete == false
|
214
|
+
status = Hpricot::XML(@conn.request(@conn.base_url+@change_url).raw_data).search("Status")
|
215
|
+
@complete = status.size > 0 && status.first.innerText == "INSYNC" ? true : false
|
216
|
+
end
|
217
|
+
return @complete
|
218
|
+
end
|
219
|
+
|
220
|
+
def pending?
|
221
|
+
#Return opposite of complete via XOR
|
222
|
+
return complete? ^ true
|
223
|
+
end
|
224
|
+
|
225
|
+
def to_s
|
226
|
+
return @raw_data
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
class DNSRecord
|
231
|
+
attr_reader :name
|
232
|
+
attr_reader :type
|
233
|
+
attr_reader :ttl
|
234
|
+
attr_reader :values
|
235
|
+
|
236
|
+
def initialize(name,type,ttl,values,zone)
|
237
|
+
@name = name
|
238
|
+
@type = type
|
239
|
+
@ttl = ttl
|
240
|
+
@values = values
|
241
|
+
@zone = zone
|
242
|
+
end
|
243
|
+
|
244
|
+
def gen_change_xml(xml,action)
|
245
|
+
xml.Change { |change|
|
246
|
+
change.Action(action.upcase)
|
247
|
+
change.ResourceRecordSet { |record|
|
248
|
+
record.Name(@name)
|
249
|
+
record.Type(@type)
|
250
|
+
record.TTL(@ttl)
|
251
|
+
record.ResourceRecords { |resources|
|
252
|
+
@values.each { |val|
|
253
|
+
resources.ResourceRecord { |record|
|
254
|
+
record.Value(val)
|
255
|
+
}
|
256
|
+
}
|
257
|
+
}
|
258
|
+
}
|
259
|
+
}
|
260
|
+
end
|
261
|
+
|
262
|
+
def delete(comment=nil)
|
263
|
+
@zone.perform_actions([{:action => "DELETE", :record => self}],comment)
|
264
|
+
end
|
265
|
+
|
266
|
+
def create(comment=nil)
|
267
|
+
@zone.perform_actions([{:action => "CREATE", :record => self}],comment)
|
268
|
+
end
|
269
|
+
|
270
|
+
#Need to modify to a param hash
|
271
|
+
def update(name,type,ttl,values,comment=nil)
|
272
|
+
prev = self.clone
|
273
|
+
@name = name unless name.nil?
|
274
|
+
@type = type unless type.nil?
|
275
|
+
@ttl = ttl unless ttl.nil?
|
276
|
+
@values = values unless values.nil?
|
277
|
+
@zone.perform_actions(self.name,[
|
278
|
+
{:action => "DELETE", :record => prev},
|
279
|
+
{:action => "CREATE", :record => self},
|
280
|
+
],comment)
|
281
|
+
end
|
282
|
+
|
283
|
+
#Returns the raw array so the developer can update large batches manually
|
284
|
+
#Need to modify to a param hash
|
285
|
+
def update_dirty(name,type,ttl,values)
|
286
|
+
prev = self.clone
|
287
|
+
@name = name unless name.nil?
|
288
|
+
@type = type unless type.nil?
|
289
|
+
@ttl = ttl unless ttl.nil?
|
290
|
+
@values = values unless values.nil?
|
291
|
+
return [{:action => "DELETE", :record => prev},
|
292
|
+
{:action => "CREATE", :record => self}]
|
293
|
+
end
|
294
|
+
|
295
|
+
def to_s
|
296
|
+
return "#{@name} #{@type} #{@ttl} #{@values}"
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
|
data/route53.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/route53/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "route53"
|
6
|
+
s.version = Route53::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = "Philip Corliss"
|
9
|
+
s.email = 'pcorlis@50projects.com'
|
10
|
+
s.homepage = 'http://github.com/pcorliss/ruby_route_53'
|
11
|
+
s.summary = "Library for Amazon's Route 53 service"
|
12
|
+
s.description = "Provides CRUD and list operations for records and zones as part of Amazon's Route 53 service."
|
13
|
+
|
14
|
+
s.required_rubygems_version = ">= 1.3.6"
|
15
|
+
|
16
|
+
s.add_development_dependency "bundler", ">= 1.0.0"
|
17
|
+
s.add_development_dependency "ruby-hmac"
|
18
|
+
s.add_development_dependency "hpricot"
|
19
|
+
s.add_development_dependency "builder"
|
20
|
+
|
21
|
+
s.files = `git ls-files`.split("\n")
|
22
|
+
s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
|
23
|
+
s.require_path = 'lib'
|
24
|
+
end
|
metadata
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: route53
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Philip Corliss
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-12-08 00:00:00 -06:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: bundler
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 0
|
31
|
+
- 0
|
32
|
+
version: 1.0.0
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: ruby-hmac
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id002
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: hpricot
|
50
|
+
prerelease: false
|
51
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
type: :development
|
60
|
+
version_requirements: *id003
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: builder
|
63
|
+
prerelease: false
|
64
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
type: :development
|
73
|
+
version_requirements: *id004
|
74
|
+
description: Provides CRUD and list operations for records and zones as part of Amazon's Route 53 service.
|
75
|
+
email: pcorlis@50projects.com
|
76
|
+
executables: []
|
77
|
+
|
78
|
+
extensions: []
|
79
|
+
|
80
|
+
extra_rdoc_files: []
|
81
|
+
|
82
|
+
files:
|
83
|
+
- .gitignore
|
84
|
+
- Gemfile
|
85
|
+
- Gemfile.lock
|
86
|
+
- README.markdown
|
87
|
+
- Rakefile
|
88
|
+
- lib/route53.rb
|
89
|
+
- lib/route53/version.rb
|
90
|
+
- route53.gemspec
|
91
|
+
has_rdoc: true
|
92
|
+
homepage: http://github.com/pcorliss/ruby_route_53
|
93
|
+
licenses: []
|
94
|
+
|
95
|
+
post_install_message:
|
96
|
+
rdoc_options: []
|
97
|
+
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
segments:
|
106
|
+
- 0
|
107
|
+
version: "0"
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
segments:
|
114
|
+
- 1
|
115
|
+
- 3
|
116
|
+
- 6
|
117
|
+
version: 1.3.6
|
118
|
+
requirements: []
|
119
|
+
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 1.3.7
|
122
|
+
signing_key:
|
123
|
+
specification_version: 3
|
124
|
+
summary: Library for Amazon's Route 53 service
|
125
|
+
test_files: []
|
126
|
+
|