roadworker 0.1.0 → 0.2.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/README.md +15 -2
- data/bin/roadwork +41 -14
- data/lib/roadworker/client.rb +20 -10
- data/lib/roadworker/dsl-tester.rb +177 -0
- data/lib/roadworker/dsl.rb +6 -0
- data/lib/roadworker/version.rb +1 -1
- metadata +19 -2
data/README.md
CHANGED
@@ -30,8 +30,8 @@ shell> export AWS_ACCESS_KEY_ID='...'
|
|
30
30
|
shell> export AWS_SECRET_ACCESS_KEY='...'
|
31
31
|
shell> roadwork -e -o Routefile
|
32
32
|
shell> vi Routefile
|
33
|
-
shell> roadwork --dry-run
|
34
|
-
shell> roudwork
|
33
|
+
shell> roadwork -a --dry-run
|
34
|
+
shell> roudwork -a
|
35
35
|
```
|
36
36
|
|
37
37
|
## Routefile example
|
@@ -72,3 +72,16 @@ hosted_zone "info.winebarrel.jp." do
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
```
|
75
|
+
|
76
|
+
## Test
|
77
|
+
|
78
|
+
Routefile compares the results of a query to the DNS and DSL in the test mode.
|
79
|
+
|
80
|
+
```
|
81
|
+
shell> roadwork -t
|
82
|
+
..F..
|
83
|
+
info.winebarrel.jp. A:
|
84
|
+
expected=127.0.0.1(300),127.0.0.3(300)
|
85
|
+
actual=127.0.0.1(300),127.0.0.2(300)
|
86
|
+
5 examples, 1 failure
|
87
|
+
```
|
data/bin/roadwork
CHANGED
@@ -7,12 +7,13 @@ require 'logger'
|
|
7
7
|
|
8
8
|
file = 'Routefile'
|
9
9
|
output_file = '-'
|
10
|
-
|
10
|
+
mode = nil
|
11
11
|
logger = Logger.new($stdout)
|
12
12
|
|
13
13
|
logger.formatter = proc {|severity, datetime, progname, msg|
|
14
14
|
"#{msg}\n"
|
15
15
|
}
|
16
|
+
|
16
17
|
options = {
|
17
18
|
:logger => logger,
|
18
19
|
:dry_run => false,
|
@@ -25,15 +26,17 @@ ARGV.options do |opt|
|
|
25
26
|
access_key = nil
|
26
27
|
secret_key = nil
|
27
28
|
|
28
|
-
opt.on('-k', '--access-key ACCESS_KEY') {|v| access_key = v
|
29
|
-
opt.on('-s', '--secret-key SECRET_KEY') {|v| secret_key = v
|
30
|
-
opt.on('-f', '--file FILE') {|v| file = v
|
31
|
-
opt.on('-
|
32
|
-
opt.on('-
|
33
|
-
opt.on('',
|
34
|
-
opt.on(''
|
35
|
-
opt.on(''
|
36
|
-
opt.on('' , '--
|
29
|
+
opt.on('-k', '--access-key ACCESS_KEY') {|v| access_key = v }
|
30
|
+
opt.on('-s', '--secret-key SECRET_KEY') {|v| secret_key = v }
|
31
|
+
opt.on('-f', '--file FILE') {|v| file = v }
|
32
|
+
opt.on('-a', '--apply') {|v| mode = :apply }
|
33
|
+
opt.on('-e', '--export') {|v| mode = :export }
|
34
|
+
opt.on('-o', '--output FILE') {|v| output_file = v }
|
35
|
+
opt.on('-t', '--test') {|v| mode = :test }
|
36
|
+
opt.on('', '--dry-run') {|v| options[:dry_run] = true }
|
37
|
+
opt.on('' , '--force') { options[:force] = true }
|
38
|
+
opt.on('' , '--no-color') { options[:color] = false }
|
39
|
+
opt.on('' , '--debug') { options[:debug] = true }
|
37
40
|
opt.parse!
|
38
41
|
|
39
42
|
if access_key and secret_key
|
@@ -41,7 +44,7 @@ ARGV.options do |opt|
|
|
41
44
|
:access_key_id => access_key,
|
42
45
|
:secret_access_key => secret_key,
|
43
46
|
})
|
44
|
-
elsif (access_key and !secret_key) or (!access_key and secret_key)
|
47
|
+
elsif (access_key and !secret_key) or (!access_key and secret_key) or mode.nil?
|
45
48
|
puts opt.help
|
46
49
|
exit 1
|
47
50
|
end
|
@@ -55,19 +58,40 @@ if options[:debug]
|
|
55
58
|
end
|
56
59
|
|
57
60
|
begin
|
61
|
+
logger = options[:logger]
|
62
|
+
logger.level = options[:debug] ? Logger::DEBUG : Logger::INFO
|
63
|
+
|
58
64
|
client = Roadworker::Client.new(options)
|
59
65
|
|
60
|
-
|
66
|
+
case mode
|
67
|
+
when :export
|
61
68
|
exported = client.export
|
62
69
|
|
63
70
|
if output_file == '-'
|
64
|
-
logger.info('Export Route53')
|
71
|
+
logger.info('# Export Route53')
|
65
72
|
puts client.export
|
66
73
|
else
|
67
74
|
logger.info("Export Route53 to `#{output_file}`")
|
68
75
|
open(output_file, 'wb') {|f| f.puts client.export }
|
69
76
|
end
|
70
|
-
|
77
|
+
when :test
|
78
|
+
# XXX:
|
79
|
+
unless File.exist?(file)
|
80
|
+
raise "No Routefile found (looking for: #{file})"
|
81
|
+
end
|
82
|
+
|
83
|
+
examples, failures = client.test(file)
|
84
|
+
examples_message = (examples > 1 ? "%d examples" : "%d example") % examples
|
85
|
+
failures_message = (failures > 1 ? "%d failures" : "%d failure") % failures
|
86
|
+
result_message = [examples_message, failures_message].join(', ')
|
87
|
+
|
88
|
+
if failures.zero?
|
89
|
+
logger.info(result_message.green)
|
90
|
+
else
|
91
|
+
logger.info(result_message.red)
|
92
|
+
exit 1
|
93
|
+
end
|
94
|
+
when :apply
|
71
95
|
unless File.exist?(file)
|
72
96
|
raise "No Routefile found (looking for: #{file})"
|
73
97
|
end
|
@@ -79,11 +103,14 @@ begin
|
|
79
103
|
updated = client.apply(file)
|
80
104
|
|
81
105
|
logger.info('No change'.intense_blue) unless updated
|
106
|
+
else
|
107
|
+
raise 'must not happen'
|
82
108
|
end
|
83
109
|
rescue => e
|
84
110
|
if options[:debug]
|
85
111
|
raise e
|
86
112
|
else
|
87
113
|
$stderr.puts e
|
114
|
+
exit 1
|
88
115
|
end
|
89
116
|
end
|
data/lib/roadworker/client.rb
CHANGED
@@ -19,16 +19,7 @@ module Roadworker
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def apply(file)
|
22
|
-
dsl =
|
23
|
-
|
24
|
-
if file.kind_of?(String)
|
25
|
-
open(file) do |f|
|
26
|
-
dsl = DSL.define(f.read, file).result
|
27
|
-
end
|
28
|
-
else
|
29
|
-
dsl = DSL.define(file.read, file.path).result
|
30
|
-
end
|
31
|
-
|
22
|
+
dsl = load_file(file)
|
32
23
|
updated = false
|
33
24
|
|
34
25
|
if dsl.hosted_zones.empty? and not @options.force
|
@@ -48,8 +39,27 @@ module Roadworker
|
|
48
39
|
DSL.convert(exported)
|
49
40
|
end
|
50
41
|
|
42
|
+
def test(file)
|
43
|
+
dsl = load_file(file)
|
44
|
+
DSL.test(dsl, @options)
|
45
|
+
end
|
46
|
+
|
51
47
|
private
|
52
48
|
|
49
|
+
def load_file(file)
|
50
|
+
dsl = nil
|
51
|
+
|
52
|
+
if file.kind_of?(String)
|
53
|
+
open(file) do |f|
|
54
|
+
dsl = DSL.define(f.read, file).result
|
55
|
+
end
|
56
|
+
else
|
57
|
+
dsl = DSL.define(file.read, file.path).result
|
58
|
+
end
|
59
|
+
|
60
|
+
return dsl
|
61
|
+
end
|
62
|
+
|
53
63
|
def walk_hosted_zones(dsl)
|
54
64
|
expected = collection_to_hash(dsl.hosted_zones, :name)
|
55
65
|
actual = collection_to_hash(@route53.hosted_zones, :name)
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'roadworker/log'
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
require 'socket'
|
5
|
+
|
6
|
+
# XXX:
|
7
|
+
unless Socket.const_defined?(:AF_INET6)
|
8
|
+
Socket::AF_INET6 = Socket::AF_INET
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'net/dns'
|
12
|
+
|
13
|
+
module Roadworker
|
14
|
+
class DSL
|
15
|
+
class Tester
|
16
|
+
include Roadworker::Log
|
17
|
+
|
18
|
+
DEFAULT_NAMESERVERS = '8.8.8.8'
|
19
|
+
ASTERISK_PREFIX = 'asterisk-of-wildcard'
|
20
|
+
|
21
|
+
class << self
|
22
|
+
def test(dsl, options)
|
23
|
+
self.new(options).test(dsl)
|
24
|
+
end
|
25
|
+
end # of class method
|
26
|
+
|
27
|
+
def initialize(options)
|
28
|
+
@options = options
|
29
|
+
@resolver = create_resolver
|
30
|
+
end
|
31
|
+
|
32
|
+
def test(dsl)
|
33
|
+
records = fetch_records(dsl)
|
34
|
+
failures = 0
|
35
|
+
error_messages = []
|
36
|
+
|
37
|
+
records.each do |key, rrs|
|
38
|
+
errors = []
|
39
|
+
|
40
|
+
name = asterisk_to_anyname(key[0])
|
41
|
+
type = key[1]
|
42
|
+
|
43
|
+
log(:debug, 'Check DNS', :white, "#{name} #{type}")
|
44
|
+
|
45
|
+
response = query(name, type)
|
46
|
+
|
47
|
+
unless response
|
48
|
+
failures += 1
|
49
|
+
print_failure
|
50
|
+
next
|
51
|
+
end
|
52
|
+
|
53
|
+
is_valid = rrs.any? {|record|
|
54
|
+
expected_value = (record.resource_records || []).map {|i| i[:value].strip }.sort
|
55
|
+
expected_ttl = record.dns_name ? 60 : record.ttl
|
56
|
+
|
57
|
+
actual_value = response.answer.map {|i| (type == 'TXT' ? i.txt : i.value).strip }.sort
|
58
|
+
actual_ttls = response.answer.map {|i| i.ttl }
|
59
|
+
|
60
|
+
case type
|
61
|
+
when 'NS', 'PTR', 'MX', 'CNAME'
|
62
|
+
expected_value = expected_value.map {|i| i.downcase.sub(/\.\Z/, '') }
|
63
|
+
actual_value = actual_value.map {|i| i.downcase.sub(/\.\Z/, '') }
|
64
|
+
when 'TXT'
|
65
|
+
expected_value = expected_value.map {|i| i.scan(/"([^"]+)"/).join.strip.gsub(/\s+/, ' ') }
|
66
|
+
actual_value = actual_value.map {|i| i.strip.gsub(/\s+/, ' ') }
|
67
|
+
end
|
68
|
+
|
69
|
+
expected_message = record.resource_records ? expected_value.map {|i| "#{i}(#{expected_ttl})" }.join(',') : "#{record.dns_name}(#{expected_ttl})"
|
70
|
+
actual_message = actual_value.zip(actual_ttls).map {|v, t| "#{v}(#{t})" }.join(',')
|
71
|
+
logmsg_expected = "expected=#{expected_message}"
|
72
|
+
logmsg_actual = "actual=#{actual_message}"
|
73
|
+
log(:debug, " #{logmsg_expected}\n #{logmsg_actual}", :white, "#{name} #{type}")
|
74
|
+
|
75
|
+
is_same = false
|
76
|
+
|
77
|
+
if record.dns_name
|
78
|
+
# A(Alias)
|
79
|
+
is_same = response.answer.all? {|a|
|
80
|
+
query(a.value, 'PTR').answer.all? do |ptr|
|
81
|
+
ptr.value =~ /\.compute\.amazonaws\.com\.\Z/
|
82
|
+
end
|
83
|
+
}
|
84
|
+
else
|
85
|
+
is_same = (expected_value == actual_value)
|
86
|
+
end
|
87
|
+
|
88
|
+
if is_same
|
89
|
+
unless actual_ttls.all? {|i| i <= expected_ttl }
|
90
|
+
is_same = false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
errors << [logmsg_expected, logmsg_actual] unless is_same
|
95
|
+
is_same
|
96
|
+
}
|
97
|
+
|
98
|
+
if is_valid
|
99
|
+
print_success
|
100
|
+
else
|
101
|
+
failures += 1
|
102
|
+
print_failure
|
103
|
+
|
104
|
+
errors.each do |logmsg_expected, logmsg_actual|
|
105
|
+
error_messages << "#{name} #{type}:\n #{logmsg_expected}\n #{logmsg_actual}"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
result &&= is_valid
|
110
|
+
end
|
111
|
+
|
112
|
+
puts unless @options.debug
|
113
|
+
|
114
|
+
error_messages.each do |msg|
|
115
|
+
log(:warn, msg, :intense_red)
|
116
|
+
end
|
117
|
+
|
118
|
+
[records.length, failures]
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
def create_resolver
|
124
|
+
log_file = @options.debug ? Net::DNS::Resolver::Defaults[:log_file] : '/dev/null'
|
125
|
+
|
126
|
+
if File.exist?(Net::DNS::Resolver::Defaults[:config_file])
|
127
|
+
Net::DNS::Resolver.new(:log_file => log_file)
|
128
|
+
else
|
129
|
+
Tempfile.open(File.basename(__FILE__)) do |f|
|
130
|
+
Net::DNS::Resolver.new(:config_file => f.path, :nameservers => DEFAULT_NAMESERVERS, :log_file => log_file)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def fetch_records(dsl)
|
136
|
+
record_list = {}
|
137
|
+
|
138
|
+
dsl.hosted_zones.each do |zone|
|
139
|
+
zone.rrsets.each do |record|
|
140
|
+
key = [record.name, record.type]
|
141
|
+
record_list[key] ||= []
|
142
|
+
record_list[key] << record
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
return record_list
|
147
|
+
end
|
148
|
+
|
149
|
+
def asterisk_to_anyname(name)
|
150
|
+
rand_str = (("a".."z").to_a + ("A".."Z").to_a + (0..9).to_a).shuffle[0..7].join
|
151
|
+
name.gsub('*', "#{ASTERISK_PREFIX}-#{rand_str}")
|
152
|
+
end
|
153
|
+
|
154
|
+
def query(name, type)
|
155
|
+
ctype = Net::DNS.const_get(type)
|
156
|
+
response = nil
|
157
|
+
|
158
|
+
begin
|
159
|
+
response = @resolver.query(name, ctype)
|
160
|
+
rescue => e
|
161
|
+
log(:warn, "WARNING #{e.message}", :yellow, "#{name} #{type}")
|
162
|
+
end
|
163
|
+
|
164
|
+
return response
|
165
|
+
end
|
166
|
+
|
167
|
+
def print_success
|
168
|
+
print '.'.intense_green unless @options.debug
|
169
|
+
end
|
170
|
+
|
171
|
+
def print_failure
|
172
|
+
print 'F'.intense_red unless @options.debug
|
173
|
+
end
|
174
|
+
|
175
|
+
end # Tester
|
176
|
+
end # DSL
|
177
|
+
end # Roadworker
|
data/lib/roadworker/dsl.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'roadworker/dsl-converter'
|
2
|
+
require 'roadworker/dsl-tester'
|
2
3
|
|
3
4
|
require 'ostruct'
|
4
5
|
|
@@ -15,6 +16,11 @@ module Roadworker
|
|
15
16
|
def convert(hosted_zones)
|
16
17
|
Converter.convert(hosted_zones)
|
17
18
|
end
|
19
|
+
|
20
|
+
|
21
|
+
def test(dsl, options)
|
22
|
+
Tester.test(dsl, options)
|
23
|
+
end
|
18
24
|
end # of class method
|
19
25
|
|
20
26
|
attr_reader :result
|
data/lib/roadworker/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roadworker
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-07-
|
12
|
+
date: 2013-07-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: aws-sdk
|
@@ -43,6 +43,22 @@ dependencies:
|
|
43
43
|
- - ! '>='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: 1.2.2
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: net-dns
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.8.0
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.8.0
|
46
62
|
- !ruby/object:Gem::Dependency
|
47
63
|
name: bundler
|
48
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -104,6 +120,7 @@ files:
|
|
104
120
|
- lib/roadworker/client.rb
|
105
121
|
- lib/roadworker/collection.rb
|
106
122
|
- lib/roadworker/dsl-converter.rb
|
123
|
+
- lib/roadworker/dsl-tester.rb
|
107
124
|
- lib/roadworker/dsl.rb
|
108
125
|
- lib/roadworker/log.rb
|
109
126
|
- lib/roadworker/route53-exporter.rb
|