route53 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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