class Roadworker::DSL::Tester
Constants
- ASTERISK_PREFIX
- DEFAULT_CONFIG_FILE
- DEFAULT_NAMESERVERS
- RETRY
- RETRY_WAIT
Public Class Methods
new(options)
click to toggle source
# File lib/roadworker/dsl-tester.rb, line 45 def initialize(options) @options = options @resolver = create_resolver end
test(dsl, options)
click to toggle source
# File lib/roadworker/dsl-tester.rb, line 40 def test(dsl, options) self.new(options).test(dsl) end
Public Instance Methods
test(dsl)
click to toggle source
# File lib/roadworker/dsl-tester.rb, line 50 def test(dsl) records = fetch_records(dsl) records_length = records.length failures = 0 error_messages = [] warning_messages = [] a_records = {} records.each do |key, rrs| name, type = key next unless type == "A" a_records[name] = rrs.map do |record| [(record.resource_records || []).map {|i| i[:value].strip }.sort, record.ttl] end end validate_record = lambda do |key, rrs, asterisk_answers| errors = [] original_name = key[0] name = asterisk_to_anyname(original_name) type = key[1] log(:debug, 'Check DNS', :white, "#{name} #{type}") response = query(name, type, error_messages) unless response failures += 1 print_failure next end is_valid = rrs.any? {|record| expected_value = (record.resource_records || []).map {|i| i[:value].strip }.sort expected_ttl = fetch_dns_name(record.dns_name) ? 60 : record.ttl actual_value = response.answer.map {|i| case type when 'TXT', 'SPF' i.data else i.rdata_to_string end }.map {|i| i.strip }.sort actual_ttls = response.answer.map {|i| i.ttl } case type when 'NS', 'PTR', 'MX', 'CNAME', 'SRV' expected_value = expected_value.map {|i| i.downcase.sub(/\.\z/, '') } actual_value = actual_value.map {|i| i.downcase.sub(/\.\z/, '') } when 'TXT', 'SPF' # see https://github.com/bluemonk/net-dns/blob/651dc1006d9ee0c167fa515e4b9d2494af415ae9/lib/net/dns/rr/txt.rb#L46 expected_value = expected_value.map {|i| i.scan(/(?:\\\\|(?:\\"|(?:[^\\"]|[^"])))*"((?:\\\\|(?:\\"|(?:\\"|(?:[^\\"]|[^"]))))*)"/).join(' ').gsub(/\\(.)/) { $1 }.strip } actual_value = actual_value.map {|i| i.strip } end if ['SRV', 'MX'].include?(type) expected_value = expected_value.map {|i| i.gsub(/\s+/, ' ') } actual_value = actual_value.map {|i| i.gsub(/\s+/, ' ') } end expected_message = record.resource_records ? expected_value.map {|i| "#{i}(#{expected_ttl})" }.join(',') : "#{fetch_dns_name(record.dns_name)}(#{expected_ttl})" actual_message = actual_value.zip(actual_ttls).map {|v, t| "#{v}(#{t})" }.join(',') logmsg_expected = "expected=#{expected_message}" logmsg_actual = "actual=#{actual_message}" log(:debug, " #{logmsg_expected}\n #{logmsg_actual}", :white) is_same = false check_ttl = true if fetch_dns_name(record.dns_name) # A(Alias) case fetch_dns_name(record.dns_name).sub(/\.\z/, '') when /\.elb\.amazonaws\.com/i check_ttl = false is_same = response.answer.all? {|a| response_query_ptr = query(a.value, 'PTR', error_messages) if response_query_ptr response_query_ptr.answer.all? do |ptr| ptr.value =~ /\.compute\.amazonaws\.com\.\z/ end else false end } when /\As3-website-(?:[^.]+)\.amazonaws\.com\z/ check_ttl = false response_answer_ip_1_2 = response.answer.map {|a| a.value.split('.').slice(0, 2) }.uniq # try 3 times is_same = (0...3).any? do |n| unless n.zero? sleep 3 log(:debug, 'Retry Check', :white, "#{name} #{type}") end dns_name_a = query(fetch_dns_name(record.dns_name), 'A', error_messages) s3_website_endpoint_ips = dns_name_a.answer.map {|i| i.value } !s3_website_endpoint_ips.empty? && s3_website_endpoint_ips.any? {|ip| response_answer_ip_1_2.include?(ip.split('.').slice(0, 2)) } end when /\.cloudfront\.net\z/ check_ttl = false is_same = response.answer.all? {|a| response_query_ptr = query(a.value, 'PTR', error_messages) if response_query_ptr response_query_ptr.answer.all? do |ptr| ptr.value =~ /\.cloudfront\.net\.\z/ end end } else if (alias_target_a_record = a_records[fetch_dns_name(record.dns_name)]) expected_message = alias_target_a_record.map {|values, ttl| values.map {|i| "#{i}(#{ttl})" }.join(',') }.uniq.join (' or ') logmsg_expected = "expected=#{expected_message}" expected_ttl = alias_target_a_record.map {|values, ttl| ttl }.max is_same = alias_target_a_record.any? {|values, ttl| values == actual_value } else warning_messages << "#{name} #{type}: Cannot check `#{fetch_dns_name(record.dns_name)}`" is_same = true end end else is_same = (expected_value == actual_value) end if is_same && check_ttl unless actual_ttls.all? {|i| i <= expected_ttl } is_same = false end end errors << [logmsg_expected, logmsg_actual] unless is_same if asterisk_answers asterisk_answers.each do |ast_key, answers| ast_name = ast_key[0] ast_regex = Regexp.new('\A' + ast_name.sub(/\.\z/, '').gsub('.', '\.').gsub('*', '.+') + '\Z') if ast_regex =~ name.sub(/\.\z/, '') and actual_value.any? {|i| answers.include?(i) } warning_messages << "#{name} #{type}: same as `#{ast_name}`" end end end is_same } if is_valid print_success else failures += 1 print_failure errors.each do |logmsg_expected, logmsg_actual| error_messages << "#{name} #{type}:\n #{logmsg_expected}\n #{logmsg_actual}" end end end asterisk_records = {} asterisk_answers = {} records.keys.each do |key| asterisk_records[key] = records.delete(key) if key[0]['*'] end asterisk_records.map do |key, rrs| original_name = key[0] name = asterisk_to_anyname(original_name) type = key[1] response = query(name, type) if response asterisk_answers[key] = response.answer.map {|i| (%w(TXT SPF).include?(type) ? i.data : i.rdata_to_string).strip } end end asterisk_records.each do |key, rrs| validate_record.call(key, rrs, nil) end records.each do |key, rrs| validate_record.call(key, rrs, asterisk_answers) end puts unless @options.debug error_messages.each do |msg| log(:error, msg, :intense_red) end warning_messages.each do |msg| log(:warn, "WARNING #{msg}", :intense_yellow) end [records_length, failures] end
Private Instance Methods
asterisk_to_anyname(name)
click to toggle source
# File lib/roadworker/dsl-tester.rb, line 301 def asterisk_to_anyname(name) rand_str = (("a".."z").to_a + ("A".."Z").to_a + (0..9).to_a).shuffle[0..7].join name.gsub('*', "#{ASTERISK_PREFIX}-#{rand_str}") end
create_resolver()
click to toggle source
# File lib/roadworker/dsl-tester.rb, line 271 def create_resolver resolver_opts = {} resolver_opts[:port] = @options.port if @options.port unless File.exist?(DEFAULT_CONFIG_FILE) resolver_opts[:nameservers] = DEFAULT_NAMESERVERS end resolver_opts[:nameservers] = @options.nameservers if @options.nameservers resolver = Dnsruby::Resolver.new(resolver_opts) resolver.do_caching = false resolver end
fetch_dns_name(dns_name)
click to toggle source
# File lib/roadworker/dsl-tester.rb, line 333 def fetch_dns_name(dns_name) if dns_name dns_name.first else nil end end
fetch_records(dsl)
click to toggle source
# File lib/roadworker/dsl-tester.rb, line 285 def fetch_records(dsl) record_list = {} dsl.hosted_zones.each do |zone| next unless matched_zone?(zone.name) zone.rrsets.each do |record| key = [record.name, record.type] record_list[key] ||= [] record_list[key] << record end end return record_list end
fix_srv_host(query_name, host)
click to toggle source
# File lib/roadworker/dsl-tester.rb, line 258 def fix_srv_host(query_name, host) if (host || '').strip.empty? query_name elsif host =~ /\x1A\z/ host = host.sub(/\x1A\z/, '') query_name = query_name.split('.') query_name.slice!(0, host.count('.')) host + query_name.join('.') else host end end
print_failure()
click to toggle source
# File lib/roadworker/dsl-tester.rb, line 329 def print_failure print Roadworker::StringHelper.intense_red('F') unless @options.debug end
print_success()
click to toggle source
# File lib/roadworker/dsl-tester.rb, line 325 def print_success print Roadworker::StringHelper.intense_green('.') unless @options.debug end
query(name, type, error_messages = nil)
click to toggle source
# File lib/roadworker/dsl-tester.rb, line 306 def query(name, type, error_messages = nil) response = nil RETRY.times do |i| begin response = @resolver.query(name, type) break rescue => e if (i + 1) < RETRY sleep RETRY_WAIT else error_messages << "#{name} #{type}: #{e.message}" if error_messages end end end return response end