route53 0.1.0 → 0.1.2

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 CHANGED
@@ -1 +1,2 @@
1
1
  pkg
2
+ route53-*.gem
data/bin/route53 ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ lib = File.expand_path(File.dirname(__FILE__) + '/../lib')
4
+ $LOAD_PATH.unshift(lib) if File.directory?(lib) && !$LOAD_PATH.include?(lib)
5
+
6
+ require 'route53'
7
+ require 'route53/cli'
8
+
9
+ app = Route53::CLI.new(ARGV, STDIN)
10
+ app.run
data/lib/route53.rb CHANGED
@@ -1,29 +1,34 @@
1
+ require 'hmac'
2
+ require 'hmac-sha2'
3
+ require 'base64'
4
+ require 'time'
5
+ require 'net/http'
6
+ require 'uri'
7
+ require 'hpricot'
8
+ require 'builder'
9
+ require 'digest/md5'
10
+
1
11
  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
 
12
13
  class Connection
13
14
  attr_reader :base_url
14
15
  attr_reader :api
15
16
  attr_reader :endpoint
17
+ attr_reader :verbose
16
18
 
17
- def initialize(accesskey,secret,api='2010-10-01',endpoint='https://route53.amazonaws.com/')
19
+ def initialize(accesskey,secret,api='2010-10-01',endpoint='https://route53.amazonaws.com/',verbose=false)
18
20
  @accesskey = accesskey
19
21
  @secret = secret
20
22
  @api = api
21
23
  @endpoint = endpoint
22
24
  @base_url = endpoint+@api
25
+ @verbose = verbose
23
26
  end
24
27
 
25
28
  def request(url,type = "GET",data = nil)
26
- puts "URL: #{url}"
29
+ puts "URL: #{url}" if @verbose
30
+ puts "Type: #{type}" if @verbose
31
+ puts "Req: #{data}" if type != "GET" && @verbose
27
32
  uri = URI(url)
28
33
  http = Net::HTTP.new(uri.host, uri.port)
29
34
  http.use_ssl = true
@@ -36,10 +41,10 @@ module Route53
36
41
  'X-Amzn-Authorization' => "AWS3-HTTPS AWSAccessKeyId=#{@accesskey},Algorithm=HmacSHA256,Signature=#{signature}",
37
42
  'Content-Type' => 'text/xml; charset=UTF-8'
38
43
  }
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)
44
+ resp, raw_resp = http.send_request(type,uri.path,data,headers)
45
+ #puts "Resp:"+resp.to_s if @verbose
46
+ #puts "XML_RESP:"+raw_resp if @verbose
47
+ return AWSResponse.new(raw_resp,self)
43
48
  end
44
49
 
45
50
  def get_zones(name = nil)
@@ -69,12 +74,19 @@ module Route53
69
74
 
70
75
  def get_date
71
76
  #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']
77
+ #Cache date for 30 seconds to reduce extra calls
78
+ if @date_stale.nil? || @date_stale < Time.now - 30
79
+ uri = URI(@endpoint)
80
+ http = Net::HTTP.new(uri.host, uri.port)
81
+ http.use_ssl = true
82
+ resp = nil
83
+ puts "Making Date Request" if @verbose
84
+ http.start { |http| resp = http.head('/date') }
85
+ @date = resp['Date']
86
+ @date_stale = Time.now
87
+ puts "Received Date." if @verbose
88
+ end
89
+ return @date
78
90
  end
79
91
 
80
92
  end
@@ -107,7 +119,7 @@ module Route53
107
119
  xml_str = ""
108
120
  xml = Builder::XmlMarkup.new(:target=>xml_str, :indent=>2)
109
121
  xml.instruct!
110
- xml.CreateHostedZoneRequest(:xmlns => @endpoint+'doc/'+@api+'/') { |create|
122
+ xml.CreateHostedZoneRequest(:xmlns => @conn.endpoint+'doc/'+@conn.api+'/') { |create|
111
123
  create.Name(@name)
112
124
  # AWS lists this as required
113
125
  # "unique string that identifies the request and that
@@ -118,7 +130,7 @@ module Route53
118
130
  conf.Comment(comment)
119
131
  }
120
132
  }
121
- puts "XML:\n#{xml_str}"
133
+ #puts "XML:\n#{xml_str}" if @conn.verbose
122
134
  @conn.request(@conn.base_url + "/hostedzone","POST",xml_str)
123
135
  end
124
136
 
@@ -133,11 +145,11 @@ module Route53
133
145
 
134
146
  dom_records = []
135
147
  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
148
+ #puts "Name:"+record.search("Name").first.innerText if @conn.verbose
149
+ #puts "Type:"+record.search("Type").first.innerText if @conn.verbose
150
+ #puts "TTL:"+record.search("TTL").first.innerText if @conn.verbose
139
151
  record.search("Value").each do |val|
140
- puts "Val:"+val.innerText
152
+ #puts "Val:"+val.innerText if @conn.verbose
141
153
  end
142
154
  dom_records.push(DNSRecord.new(record.search("Name").first.innerText,
143
155
  record.search("Type").first.innerText,
@@ -170,7 +182,7 @@ module Route53
170
182
  }
171
183
  }
172
184
  }
173
- puts "XML:\n#{xml_str}"
185
+ #puts "XML:\n#{xml_str}" if @conn.verbose
174
186
  return xml_str
175
187
  end
176
188
 
@@ -195,6 +207,7 @@ module Route53
195
207
  $stderr.puts @raw_data
196
208
  end
197
209
  @conn = conn
210
+ puts "Raw: #{@raw_data}" if @conn.verbose
198
211
  end
199
212
 
200
213
  def error?
@@ -202,6 +215,7 @@ module Route53
202
215
  end
203
216
 
204
217
  def complete?
218
+ return true if error?
205
219
  if @change_url.nil?
206
220
  change = Hpricot::XML(@raw_data).search("ChangeInfo")
207
221
  if change.size > 0
@@ -274,10 +288,10 @@ module Route53
274
288
  @type = type unless type.nil?
275
289
  @ttl = ttl unless ttl.nil?
276
290
  @values = values unless values.nil?
277
- @zone.perform_actions(self.name,[
278
- {:action => "DELETE", :record => prev},
279
- {:action => "CREATE", :record => self},
280
- ],comment)
291
+ @zone.perform_actions([
292
+ {:action => "DELETE", :record => prev},
293
+ {:action => "CREATE", :record => self},
294
+ ],comment)
281
295
  end
282
296
 
283
297
  #Returns the raw array so the developer can update large batches manually
@@ -0,0 +1,338 @@
1
+
2
+ require 'rubygems'
3
+ require 'optparse'
4
+ require 'ostruct'
5
+ require 'date'
6
+ require 'yaml'
7
+ require 'route53/version'
8
+
9
+ module Route53
10
+ class CLI
11
+
12
+ attr_reader :options
13
+
14
+ def initialize(arguments, stdin)
15
+ @arguments = arguments
16
+ @stdin = stdin
17
+
18
+ # Set defaults
19
+ @options = OpenStruct.new
20
+ @options.verbose = false
21
+ @options.quiet = false
22
+ end
23
+
24
+ # Parse options, check arguments, then process the command
25
+ def run
26
+ if parsed_options? && arguments_valid?
27
+ puts "Start at #{DateTime.now}\n\n" if @options.verbose
28
+
29
+ output_options if @options.verbose # [Optional]
30
+
31
+ process_arguments
32
+ process_command
33
+
34
+ puts "\nFinished at #{DateTime.now}" if @options.verbose
35
+
36
+ else
37
+ #puts "Usage Message"
38
+ end
39
+
40
+ end
41
+
42
+ protected
43
+
44
+ def parsed_options?
45
+
46
+ # Specify options
47
+ opts = OptionParser.new
48
+ opts.on('-v', '--version', "Print Version Information") { output_version ; exit 0 }
49
+ opts.on('-h', '--help',"Show this message") { puts opts ; exit 0 }
50
+ opts.on('-V', '--verbose',"Verbose Output") { @options.verbose = true }
51
+ #opts.on('-q', '--quiet',"Quiet Output") { @options.quiet = true }
52
+
53
+ opts.on('-l', '--list [ZONE]', String, "Receive a list of all zones or specify a zone to view") { |zone| @options.zone = zone; @options.list = true }
54
+ opts.on('-n', '--new [ZONE]', String, "Create a new Zone") { |zone| @options.zone = zone; @options.new_zone = true }
55
+ opts.on('-d', '--delete [ZONE]', String, "Delete a Zone") { |zone| @options.zone = zone; @options.delete_zone = true }
56
+ opts.on('-z', '--zone [ZONE]', String, "Specify a zone to perform an operation on") { |zone| @options.zone = zone }
57
+
58
+ opts.on('-c', '--create', "Create a new record") { @options.create_record = true }
59
+ opts.on('-r', '--remove', String, "Remove a record") { |record| @options.remove_record = true }
60
+ opts.on('-g', '--change', String, "Change a record") { |record| @options.change_record = true }
61
+
62
+ opts.on('--name [NAME]', String, "Specify a name for a record") { |name| @options.name = name }
63
+ opts.on('--type [TYPE]', String, "Specify a type for a record") { |type| @options.type = type }
64
+ opts.on('--ttl [TTL]', String, "Specify a TTL for a record") { |ttl| @options.ttl = ttl }
65
+ opts.on('--values [VALUE1],[VALUE2],[VALUE3]', Array, "Specify one or multiple values for a record") { |value| @options.values = value }
66
+
67
+ opts.on('-m', '--comment [COMMENT]', String, "Provide a comment for this operation") { |comment| @options.comment = comment }
68
+
69
+ opts.on('--no-wait',"Do not wait for actions to finish syncing.") { @options.nowait = true }
70
+ opts.on('-s', '--setup',"Run the setup ptogram to create your configuration file.") { @options.setup = true }
71
+ opts.on('-f', '--file [CONFIGFILE]',String,"Specify a configuration file to use") { |file| @options.file = file }
72
+
73
+ opts.on('--access [ACCESSKEY]',String,"Specify an access key on the command line.") { |access| @options.access = access }
74
+ opts.on('--secret [SECRETKEY]',String,"Specify a secret key on the command line. WARNING: Not a good idea") { |secret| @options.secret = secret }
75
+
76
+ opts.parse!(@arguments) rescue return false
77
+
78
+ process_options
79
+ true
80
+ end
81
+
82
+ # Performs post-parse processing on options
83
+ def process_options
84
+ @options.verbose = false if @options.quiet
85
+ @options.file = (user_home+"/.route53") if @options.file.nil?
86
+ #setup file
87
+ if @options.setup
88
+ setup
89
+ end
90
+ load_config
91
+ @config['access_key'] = @options.access unless @options.access.nil?
92
+ @config['secret_key'] = @options.secret unless @options.secret.nil?
93
+ end
94
+
95
+ def output_options
96
+ puts "Options:\n"
97
+
98
+ @options.marshal_dump.each do |name, val|
99
+ puts " #{name} = #{val}"
100
+ end
101
+ end
102
+
103
+ def arguments_valid?
104
+ true #if @arguments.length == 1
105
+ end
106
+
107
+ # Setup the arguments
108
+ def process_arguments
109
+
110
+
111
+ if @options.new_zone
112
+ new_zone = Route53::Zone.new(@options.zone,nil,conn)
113
+ puts "Creating New Zone #{@options.zone}"
114
+ resp = new_zone.create_zone(@options.comment)
115
+ if resp.error?
116
+ $stderr.puts "ERROR: Failed to create new zone."
117
+ else
118
+ pending_wait(resp)
119
+ puts "Zone Created."
120
+ end
121
+ end
122
+
123
+ if @options.delete_zone
124
+ records = conn.get_zones(@options.zone)
125
+ if records.size > 0
126
+ if records.size > 1
127
+ records = record_picker(records)
128
+ end
129
+ records.each do |r|
130
+ puts "Deleting Zone #{r.name}"
131
+ resp = r.delete_zone
132
+ pending_wait(resp)
133
+ puts "Zone Deleted." unless resp.error?
134
+ end
135
+ else
136
+ $stderr.puts "ERROR: Couldn't Find Record for @options.zone."
137
+ end
138
+ end
139
+
140
+ if @options.create_record
141
+ zones = conn.get_zones(@options.zone)
142
+ if zones.size > 0
143
+ resps = []
144
+ zones.each do |z|
145
+ puts "Creating Record"
146
+ record = Route53::DNSRecord.new(@options.name,@options.type,@options.ttl,@options.values,z)
147
+ puts "Creating Record #{record}"
148
+ resps.push(record.create)
149
+ end
150
+ resps.each do |resp|
151
+ pending_wait(resp)
152
+ puts "Record Created." unless resp.error?
153
+ end
154
+ else
155
+ $stderr.puts "ERROR: Couldn't Find Record for @options.zone."
156
+ end
157
+
158
+ end
159
+
160
+ if @options.remove_record
161
+ zones = conn.get_zones(@options.zone)
162
+ if zones.size > 0
163
+ zones.each do |z|
164
+ records = z.get_records(@options.type.nil? ? "ANY" : @options.type)
165
+ if records.size > 0
166
+ if records.size > 1
167
+ records = record_picker(records)
168
+ end
169
+ records.each do |r|
170
+ puts "Deleting Record #{r.name}"
171
+ resp = r.delete
172
+ pending_wait(resp)
173
+ puts "Record Deleted." unless resp.error?
174
+ end
175
+ else
176
+ $stderr.puts "ERROR: Couldn't Find Record for @options.zone of type "+(@options.type.nil? ? "ANY" : @options.type)+"."
177
+ end
178
+ end
179
+ else
180
+ $stderr.puts "ERROR: Couldn't Find Record for @options.zone."
181
+ end
182
+ end
183
+
184
+ if @options.change_record
185
+ zones = conn.get_zones(@options.zone)
186
+ if zones.size > 0
187
+ zones.each do |z|
188
+ records = z.get_records(@options.type.nil? ? "ANY" : @options.type)
189
+ if records.size > 0
190
+ if records.size > 1
191
+ records = record_picker(records,false)
192
+ end
193
+ records.each do |r|
194
+ puts "Modifying Record #{r.name}"
195
+ resp = r.update(@options.name,@options.type,@options.ttl,@options.values,comment=nil)
196
+ pending_wait(resp)
197
+ puts "Record Modified." unless resp.error?
198
+ end
199
+ else
200
+ $stderr.puts "ERROR: Couldn't Find Record for @options.zone of type "+(@options.type.nil? ? "ANY" : @options.type)+"."
201
+ end
202
+ end
203
+ else
204
+ $stderr.puts "ERROR: Couldn't Find Record for @options.zone."
205
+ end
206
+ end
207
+
208
+ if @options.list || @options.zone.nil?
209
+ zones = conn.get_zones(@options.zone)
210
+ zones.each do |z|
211
+ puts z
212
+ if @options.zone
213
+ records = z.get_records(@options.type.nil? ? "ANY" : @options.type)
214
+ records.each do |r|
215
+ puts r
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
221
+
222
+ def setup
223
+ puts "You've either elected to run the setup or a configuration file could not be found."
224
+ puts "Please answer the following prompts."
225
+ new_config = Hash.new
226
+ new_config['access_key'] = get_input(String,"Amazon Access Key",)
227
+ new_config['secret_key'] = get_input(String,"Amazon Secret Key")
228
+ new_config['api'] = get_input(String,"Amazon Route 53 API Version","2010-10-01")
229
+ new_config['endpoint'] = get_input(String,"Amazon Route 53 Endpoint","https://route53.amazonaws.com/")
230
+ if get_input(true.class,"Save the configuration file to \"~/.route53\"?","Y")
231
+ File.open(@options.file,'w') do |out|
232
+ YAML.dump(new_config,out)
233
+ end
234
+ load_config
235
+ else
236
+ puts "Not Saving File. Dumping Config instead."
237
+ puts YAML.dump(new_config)
238
+ exit 0
239
+ end
240
+
241
+ end
242
+
243
+ def get_input(type,description,default = nil)
244
+ print "#{description}: [#{default}] "
245
+ STDOUT.flush
246
+ selection = gets
247
+ selection.chomp!
248
+ if selection == ""
249
+ selection = default
250
+ end
251
+ if type == true.class
252
+ selection = (selection == 'Y')
253
+ end
254
+ return selection
255
+ end
256
+
257
+ def record_picker(records,allowall = true)
258
+ puts "Please select the record to perform the action on."
259
+ records.each_with_index do |r,i|
260
+ puts "[#{i}] #{r}"
261
+ end
262
+ puts "[#{records.size}] All" if allowall
263
+ puts "[#{records.size+1}] None"
264
+ print "Make a selection: [#{records.size+1}] "
265
+ STDOUT.flush
266
+ selection = gets
267
+ selection.chomp!
268
+ if selection == ""
269
+ selection = records.size+1
270
+ elsif selection != "0" && selection.to_i == 0
271
+ $stderr.puts "a Invalid selection: #{selection}"
272
+ exit 1
273
+ end
274
+ selection = selection.to_i
275
+ puts "Received #{selection}"
276
+ if selection == records.size && allowall
277
+ return records
278
+ elsif selection == records.size + 1
279
+ return []
280
+ elsif records[selection]
281
+ return [records[selection]]
282
+ else
283
+ $stderr.puts "Invalid selection: #{selection}"
284
+ exit 1
285
+ end
286
+ end
287
+
288
+ def pending_wait(resp)
289
+ while !@options.nowait && resp.pending?
290
+ print '.'
291
+ sleep 1
292
+ end
293
+ end
294
+
295
+ def output_version
296
+ puts "Ruby route53 interface version #{Route53::VERSION}"
297
+ puts "Written by Philip Corliss (pcorliss@50projects.com)"
298
+ puts "https://github.com/pcorliss/ruby_route_53"
299
+ end
300
+
301
+ def process_command
302
+
303
+ end
304
+
305
+ def process_standard_input
306
+ input = @stdin.read
307
+ #@stdin.each do |line|
308
+ #
309
+ #end
310
+ end
311
+
312
+ def conn
313
+ if @conn.nil?
314
+ @conn = Route53::Connection.new(@config['access_key'],@config['secret_key'],@config['api'],@config['endpoint'],@options.verbose)
315
+ end
316
+ return @conn
317
+ end
318
+
319
+ def load_config
320
+ unless File.exists?(@options.file)
321
+ setup
322
+ end
323
+ @config = YAML.load_file(@options.file)
324
+ unless @config
325
+ @config = Hash.new
326
+ end
327
+ end
328
+
329
+ def user_home
330
+ homes = ["HOME", "HOMEPATH"]
331
+ realHome = homes.detect {|h| ENV[h] != nil}
332
+ if not realHome
333
+ $stderr.puts "Could not find home directory"
334
+ end
335
+ return ENV[realHome]
336
+ end
337
+ end
338
+ end
@@ -1,3 +1,3 @@
1
1
  module Route53
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.2"
3
3
  end
data/route53.gemspec CHANGED
@@ -13,10 +13,10 @@ Gem::Specification.new do |s|
13
13
 
14
14
  s.required_rubygems_version = ">= 1.3.6"
15
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"
16
+ s.add_dependency "bundler", ">= 1.0.0"
17
+ s.add_dependency "ruby-hmac"
18
+ s.add_dependency "hpricot"
19
+ s.add_dependency "builder"
20
20
 
21
21
  s.files = `git ls-files`.split("\n")
22
22
  s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 1
8
- - 0
9
- version: 0.1.0
8
+ - 2
9
+ version: 0.1.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - Philip Corliss
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-12-08 00:00:00 -06:00
17
+ date: 2010-12-09 00:00:00 -06:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -30,7 +30,7 @@ dependencies:
30
30
  - 0
31
31
  - 0
32
32
  version: 1.0.0
33
- type: :development
33
+ type: :runtime
34
34
  version_requirements: *id001
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: ruby-hmac
@@ -43,7 +43,7 @@ dependencies:
43
43
  segments:
44
44
  - 0
45
45
  version: "0"
46
- type: :development
46
+ type: :runtime
47
47
  version_requirements: *id002
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: hpricot
@@ -56,7 +56,7 @@ dependencies:
56
56
  segments:
57
57
  - 0
58
58
  version: "0"
59
- type: :development
59
+ type: :runtime
60
60
  version_requirements: *id003
61
61
  - !ruby/object:Gem::Dependency
62
62
  name: builder
@@ -69,12 +69,12 @@ dependencies:
69
69
  segments:
70
70
  - 0
71
71
  version: "0"
72
- type: :development
72
+ type: :runtime
73
73
  version_requirements: *id004
74
74
  description: Provides CRUD and list operations for records and zones as part of Amazon's Route 53 service.
75
75
  email: pcorlis@50projects.com
76
- executables: []
77
-
76
+ executables:
77
+ - route53
78
78
  extensions: []
79
79
 
80
80
  extra_rdoc_files: []
@@ -85,7 +85,9 @@ files:
85
85
  - Gemfile.lock
86
86
  - README.markdown
87
87
  - Rakefile
88
+ - bin/route53
88
89
  - lib/route53.rb
90
+ - lib/route53/cli.rb
89
91
  - lib/route53/version.rb
90
92
  - route53.gemspec
91
93
  has_rdoc: true