resolv-srv 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.yardopts +1 -0
- data/LICENSE +22 -0
- data/NEWS.md +11 -0
- data/README.md +135 -0
- data/Rakefile +220 -0
- data/lib/resolv-srv.rb +67 -0
- data/spec/each_srv_resource_spec.rb +146 -0
- metadata +168 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: b52d06ac3497bab2fa8922e14a2c2bcb1d9fb639
|
4
|
+
data.tar.gz: b3af0aaa18a42710a43312089286807e1b75fe0d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 039252fd5c46c66b71ca38b444c364ca93294b00e151f1ced4b99a66ae73b3e8652c6e3234570266ef1ec00e915e3a84c4a33fd20dddd77e3e54505745bf1018
|
7
|
+
data.tar.gz: 86e6293c859759d2c3b43a2db1e1428d002a0f6e1d932242e102daa563b6c9c6c4c473c4c05e79c8e2f672adb4dadb6e69ea7340d18f4c054921696ce6fbd5fe
|
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--protected --private --main README.md lib/**/*.rb - NEWS.md LICENSE
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
(The MIT License)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Jeremy Bopp
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/NEWS.md
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# News and Notifications by Version
|
2
|
+
|
3
|
+
This file lists noteworthy changes which may affect users of this project. More
|
4
|
+
detailed information is available in the rest of the documentation.
|
5
|
+
|
6
|
+
**NOTE:** Date stamps in the following entries are in YYYY/MM/DD format.
|
7
|
+
|
8
|
+
|
9
|
+
## v0.0.1 (2015/08/03)
|
10
|
+
|
11
|
+
* Birthday
|
data/README.md
ADDED
@@ -0,0 +1,135 @@
|
|
1
|
+
# Resolv SRV
|
2
|
+
|
3
|
+
Resolve and iterate over SRV DNS records correctly.
|
4
|
+
|
5
|
+
## Links
|
6
|
+
|
7
|
+
* Homepage :: https://github.com/javanthropus/resolv-srv
|
8
|
+
* Source :: https://github.com/javanthropus/resolv-srv.git
|
9
|
+
|
10
|
+
## Description
|
11
|
+
|
12
|
+
This gem patches the Resolv::DNS class in stdlib to include a method to resolve
|
13
|
+
and iterate over SRV records according to their relative priorities and weights.
|
14
|
+
|
15
|
+
## Features
|
16
|
+
|
17
|
+
* Iterate over SRV resources in order by priority and randomly in proportion to
|
18
|
+
weight.
|
19
|
+
|
20
|
+
## Known Bugs/Limitations
|
21
|
+
|
22
|
+
* None so far...
|
23
|
+
|
24
|
+
## Synopsis
|
25
|
+
|
26
|
+
Look up your user information in Active Directory (assumes `/etc/ssl/certs`
|
27
|
+
contains the internal CA certificate for the domain):
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
#!/usr/bin/env ruby
|
31
|
+
|
32
|
+
require 'net/ldap'
|
33
|
+
require 'pp'
|
34
|
+
require 'resolv-srv'
|
35
|
+
|
36
|
+
def search_ldap(domain, username, password, search_args = {})
|
37
|
+
base = domain.split('.').map { |n| "dc=#{n}" }.join(',')
|
38
|
+
Resolv::DNS.open do |dns|
|
39
|
+
dns.each_srv_resource('ldap', 'tcp', domain) do |srv|
|
40
|
+
begin
|
41
|
+
Net::LDAP.open(
|
42
|
+
host: srv.target.to_s,
|
43
|
+
port: srv.port,
|
44
|
+
base: base,
|
45
|
+
auth: {
|
46
|
+
method: :simple,
|
47
|
+
username: username,
|
48
|
+
password: password,
|
49
|
+
},
|
50
|
+
encryption: {
|
51
|
+
method: :start_tls,
|
52
|
+
tls_options: { ca_path: '/etc/ssl/certs' }
|
53
|
+
},
|
54
|
+
) do |ldap|
|
55
|
+
return ldap.search(search_args)
|
56
|
+
end
|
57
|
+
rescue Net::LDAP::Error, OpenSSL::SSL::SSLError
|
58
|
+
puts "Failed with host #{srv.target} on port #{srv.port}: #{$!}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
print 'AD Domain: '
|
65
|
+
domain = gets.chomp
|
66
|
+
print 'AD Username: '
|
67
|
+
username = gets.chomp
|
68
|
+
print "AD Password (#{username}): "
|
69
|
+
password = ($stdin.tty? ? $stdin.noecho(&:gets) : $stdin.gets).chomp
|
70
|
+
puts
|
71
|
+
|
72
|
+
pp search_ldap(
|
73
|
+
domain,
|
74
|
+
"#{username}@#{domain}",
|
75
|
+
password,
|
76
|
+
filter: "sAMAccountName=#{username}"
|
77
|
+
)
|
78
|
+
```
|
79
|
+
|
80
|
+
## Requirements
|
81
|
+
|
82
|
+
None
|
83
|
+
|
84
|
+
## Contributing
|
85
|
+
|
86
|
+
Contributions for bug fixes, documentation, extensions, tests, etc. are
|
87
|
+
encouraged.
|
88
|
+
|
89
|
+
1. Clone the repository.
|
90
|
+
2. Fix a bug or add a feature.
|
91
|
+
3. Add tests for the fix or feature.
|
92
|
+
4. Make a pull request.
|
93
|
+
|
94
|
+
## Development
|
95
|
+
|
96
|
+
After checking out the source, run:
|
97
|
+
|
98
|
+
$ bundle install
|
99
|
+
$ bundle exec rake test yard
|
100
|
+
|
101
|
+
This will install all dependencies, run the tests/specs, and generate the
|
102
|
+
documentation.
|
103
|
+
|
104
|
+
## Authors
|
105
|
+
|
106
|
+
Thanks to all contributors. Without your help this project would not exist.
|
107
|
+
|
108
|
+
* Jeremy Bopp :: jeremy@bopp.net
|
109
|
+
|
110
|
+
## License
|
111
|
+
|
112
|
+
```
|
113
|
+
(The MIT License)
|
114
|
+
|
115
|
+
Copyright (c) 2015 Jeremy Bopp
|
116
|
+
|
117
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
118
|
+
a copy of this software and associated documentation files (the
|
119
|
+
'Software'), to deal in the Software without restriction, including
|
120
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
121
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
122
|
+
permit persons to whom the Software is furnished to do so, subject to
|
123
|
+
the following conditions:
|
124
|
+
|
125
|
+
The above copyright notice and this permission notice shall be
|
126
|
+
included in all copies or substantial portions of the Software.
|
127
|
+
|
128
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
129
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
130
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
131
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
132
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
133
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
134
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
135
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# -*- ruby -*-
|
3
|
+
|
4
|
+
require 'erb'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'rubygems/package_task'
|
7
|
+
require 'rake/clean'
|
8
|
+
require 'yard'
|
9
|
+
|
10
|
+
# Load the gemspec file for this project.
|
11
|
+
GEMSPEC = Dir['*.gemspec'].first
|
12
|
+
SPEC = eval(File.read(GEMSPEC), nil, GEMSPEC)
|
13
|
+
|
14
|
+
# A dynamically generated list of files that should match the manifest (the
|
15
|
+
# combined contents of SPEC.files and SPEC.test_files). The idea is for this
|
16
|
+
# list to contain all project files except for those that have been explicitly
|
17
|
+
# excluded. This list will be compared with the manifest from the SPEC in order
|
18
|
+
# to help catch the addition or removal of files to or from the project that
|
19
|
+
# have not been accounted for either by an exclusion here or an inclusion in the
|
20
|
+
# SPEC manifest.
|
21
|
+
#
|
22
|
+
# NOTE:
|
23
|
+
# It is critical that the manifest is *not* automatically generated via globbing
|
24
|
+
# and the like; otherwise, this will yield a simple comparison between
|
25
|
+
# redundantly generated lists of files that probably will not protect the
|
26
|
+
# project from the unintentional inclusion or exclusion of files in the
|
27
|
+
# distribution.
|
28
|
+
PKG_FILES = FileList.new(Dir.glob('**/*', File::FNM_DOTMATCH)) do |files|
|
29
|
+
# Exclude anything that doesn't exist as well as directories.
|
30
|
+
files.exclude {|file| ! File.exist?(file) || File.directory?(file)}
|
31
|
+
# Exclude Git administrative files.
|
32
|
+
files.exclude(%r{(^|[/\\])\.git(ignore|modules|keep)?([/\\]|$)})
|
33
|
+
# Exclude editor swap/temporary files.
|
34
|
+
files.exclude('**/.*.sw?')
|
35
|
+
# Exclude the gemspec file.
|
36
|
+
files.exclude(GEMSPEC)
|
37
|
+
# Exclude the README template file.
|
38
|
+
files.exclude('README.md.erb')
|
39
|
+
# Exclude resources for bundler.
|
40
|
+
files.exclude('Gemfile', 'Gemfile.lock')
|
41
|
+
files.exclude(%r{^.bundle([/\\]|$)})
|
42
|
+
files.exclude(%r{^vendor/bundle([/\\]|$)})
|
43
|
+
# Exclude generated content, except for the README file.
|
44
|
+
files.exclude(%r{^(pkg|doc|.yardoc)([/\\]|$)})
|
45
|
+
# Exclude Rubinius compiled Ruby files.
|
46
|
+
files.exclude('**/*.rbc')
|
47
|
+
end
|
48
|
+
|
49
|
+
# Make sure that :clean and :clobber will not whack the repository files.
|
50
|
+
CLEAN.exclude('.git/**')
|
51
|
+
# Vim swap files are fair game for clean up.
|
52
|
+
CLEAN.include('**/.*.sw?')
|
53
|
+
|
54
|
+
# Returns the value of the VERSION environment variable as a Gem::Version object
|
55
|
+
# assuming it is set and a valid Gem version string. Otherwise, raises an
|
56
|
+
# exception.
|
57
|
+
def get_version_argument
|
58
|
+
version = ENV['VERSION']
|
59
|
+
if version.to_s.empty?
|
60
|
+
raise "No version specified: Add VERSION=X.Y.Z to the command line"
|
61
|
+
end
|
62
|
+
begin
|
63
|
+
Gem::Version.create(version.dup)
|
64
|
+
rescue ArgumentError
|
65
|
+
raise "Invalid version specified in `VERSION=#{version}'"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Performs an in place, per line edit of the file indicated by _path_ by calling
|
70
|
+
# the sub method on each line and passing _pattern_, _replacement_, and _b_ as
|
71
|
+
# arguments.
|
72
|
+
def file_sub(path, pattern, replacement = nil, &b)
|
73
|
+
tmp_path = "#{path}.tmp"
|
74
|
+
File.open(path) do |infile|
|
75
|
+
File.open(tmp_path, 'w') do |outfile|
|
76
|
+
infile.each do |line|
|
77
|
+
outfile.write(line.sub(pattern, replacement, &b))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
File.rename(tmp_path, path)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Updates the version string in the gemspec file to the string in _version_.
|
85
|
+
def set_version(version)
|
86
|
+
file_sub(GEMSPEC, /(\.version\s*=\s*).*/, "\\1'#{version}'")
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a string that is line wrapped at word boundaries, where each line is
|
90
|
+
# no longer than _line_width_ characters.
|
91
|
+
#
|
92
|
+
# This is mostly lifted directly from ActionView::Helpers::TextHelper.
|
93
|
+
def word_wrap(text, line_width = 80)
|
94
|
+
text.split("\n").collect do |line|
|
95
|
+
line.length > line_width ?
|
96
|
+
line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip :
|
97
|
+
line
|
98
|
+
end * "\n"
|
99
|
+
end
|
100
|
+
|
101
|
+
desc 'Alias for build:gem'
|
102
|
+
task :build => 'build:gem'
|
103
|
+
|
104
|
+
# Build related tasks.
|
105
|
+
namespace :build do
|
106
|
+
# Create the gem and package tasks.
|
107
|
+
Gem::PackageTask.new(SPEC).define
|
108
|
+
|
109
|
+
# Ensure that the manifest is consulted when building the gem. Any
|
110
|
+
# generated/compiled files should be available at that time.
|
111
|
+
task :gem => :check_manifest
|
112
|
+
|
113
|
+
desc 'Verify the manifest'
|
114
|
+
task :check_manifest do
|
115
|
+
manifest_files = (SPEC.files + SPEC.test_files).sort.uniq
|
116
|
+
pkg_files = PKG_FILES.sort.uniq
|
117
|
+
if manifest_files != pkg_files then
|
118
|
+
common_files = manifest_files & pkg_files
|
119
|
+
manifest_files -= common_files
|
120
|
+
pkg_files -= common_files
|
121
|
+
message = ["The manifest does not match the automatic file list."]
|
122
|
+
unless manifest_files.empty? then
|
123
|
+
message << " Extraneous files:\n " + manifest_files.join("\n ")
|
124
|
+
end
|
125
|
+
unless pkg_files.empty?
|
126
|
+
message << " Missing files:\n " + pkg_files.join("\n ")
|
127
|
+
end
|
128
|
+
raise message.join("\n")
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Creates the README.md file from its template and other sources.
|
133
|
+
file 'README.md' => ['README.md.erb', 'LICENSE', GEMSPEC] do
|
134
|
+
spec = SPEC
|
135
|
+
File.open('README.md', 'w') do |readme|
|
136
|
+
readme.write(
|
137
|
+
ERB.new(File.read('README.md.erb'), nil, '-').result(binding)
|
138
|
+
)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Ensure that the clobber task also clobbers package files.
|
144
|
+
task :clobber => 'build:clobber_package'
|
145
|
+
|
146
|
+
# Create the documentation task.
|
147
|
+
YARD::Rake::YardocTask.new
|
148
|
+
# Ensure that the README file is (re)generated first.
|
149
|
+
task :yard => 'README.md'
|
150
|
+
|
151
|
+
# Gem related tasks.
|
152
|
+
namespace :gem do
|
153
|
+
desc 'Alias for build:gem'
|
154
|
+
task :build => 'build:gem'
|
155
|
+
|
156
|
+
desc 'Publish the gemfile'
|
157
|
+
task :publish => ['version:check', :test, 'repo:tag', :build] do
|
158
|
+
sh "gem push pkg/#{SPEC.name}-#{SPEC.version}*.gem"
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
Rake::TestTask.new do |t|
|
163
|
+
t.pattern = 'spec/**/*_spec.rb'
|
164
|
+
end
|
165
|
+
|
166
|
+
# Version string management tasks.
|
167
|
+
namespace :version do
|
168
|
+
desc 'Set the version for the project to a specified version'
|
169
|
+
task :set do
|
170
|
+
set_version(get_version_argument)
|
171
|
+
end
|
172
|
+
|
173
|
+
desc 'Set the version for the project back to 0.0.0'
|
174
|
+
task :reset do
|
175
|
+
set_version('0.0.0')
|
176
|
+
end
|
177
|
+
|
178
|
+
desc 'Check that all version strings are correctly set'
|
179
|
+
task :check => ['version:check:spec', 'version:check:news']
|
180
|
+
|
181
|
+
namespace :check do
|
182
|
+
desc 'Check that the version in the gemspec is correctly set'
|
183
|
+
task :spec do
|
184
|
+
version = get_version_argument
|
185
|
+
if version != SPEC.version
|
186
|
+
raise "The given version `#{version}' does not match the gemspec version `#{SPEC.version}'"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
desc 'Check that the NEWS.md file mentions the version'
|
191
|
+
task :news do
|
192
|
+
version = get_version_argument
|
193
|
+
begin
|
194
|
+
File.open('NEWS.md') do |news|
|
195
|
+
unless news.each_line.any? {|l| l =~ /^## v#{Regexp.escape(version.to_s)} /}
|
196
|
+
raise "The NEWS.md file does not mention version `#{version}'"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
rescue Errno::ENOENT
|
200
|
+
raise 'No NEWS.md file found'
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Repository and workspace management tasks.
|
207
|
+
namespace :repo do
|
208
|
+
desc 'Tag the current HEAD with the version string'
|
209
|
+
task :tag => :check_workspace do
|
210
|
+
version = get_version_argument
|
211
|
+
sh "git tag -s -m 'Release v#{version}' v#{version}"
|
212
|
+
end
|
213
|
+
|
214
|
+
desc 'Ensure the workspace is fully committed and clean'
|
215
|
+
task :check_workspace => ['README.md'] do
|
216
|
+
unless `git status --untracked-files=all --porcelain`.empty?
|
217
|
+
raise 'Workspace has been modified. Commit pending changes and try again.'
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
data/lib/resolv-srv.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
|
3
|
+
##
|
4
|
+
# A monkey patch for Resolv::DNS that provides a mostly RFC-compliant method for
|
5
|
+
# resolving SRV records.
|
6
|
+
class Resolv::DNS
|
7
|
+
##
|
8
|
+
# Iterates over SRV resources for _service_ operating over _protocol_ within
|
9
|
+
# _domain_, first in order of priority and then randomly within a priority in
|
10
|
+
# proportion to weight.
|
11
|
+
#
|
12
|
+
# *NOTE:* The algorithm used causes resources with weight 0 to be selected
|
13
|
+
# before resources with higher weights slightly more often than they would be
|
14
|
+
# if strict RFC compliance were enforced.
|
15
|
+
#
|
16
|
+
# @param service [String] The service type, such as +ldap+ or +http+.
|
17
|
+
# @param protocol [String] The protocol for connections, such as +tcp+ or
|
18
|
+
# +udp+.
|
19
|
+
# @param domain [String] The DNS domain in which to search for records.
|
20
|
+
#
|
21
|
+
# @yield Resolv::DNS::Resource::IN::SRV
|
22
|
+
def each_srv_resource(service, protocol, domain)
|
23
|
+
if service.nil? || service.empty? || service.index('.')
|
24
|
+
raise ArgumentError, "Invalid service name: #{service.inspect}"
|
25
|
+
end
|
26
|
+
if protocol.nil? || protocol.empty? || protocol.index('.')
|
27
|
+
raise ArgumentError, "Invalid protocol name: #{protocol.inspect}"
|
28
|
+
end
|
29
|
+
|
30
|
+
name = "_#{service}._#{protocol}.#{domain}"
|
31
|
+
|
32
|
+
# Fetch the resources.
|
33
|
+
getresources(name, Resolv::DNS::Resource::IN::SRV).
|
34
|
+
# Group and sort them by priority.
|
35
|
+
sort_by!(&:priority).chunk(&:priority).sort.
|
36
|
+
# Iterate over the lists of resources at each priority level.
|
37
|
+
each do |priority, available|
|
38
|
+
# NOTE:
|
39
|
+
# All weight processing is shifted to be 1-based rather than 0-based.
|
40
|
+
# Because of the way selection is handled, this avoids needing to shuffle
|
41
|
+
# or sort the array elements by weight while ensuring that resources with
|
42
|
+
# weight 0 avoid ALWAYS being selected last. The trade-off is that
|
43
|
+
# resources with weight 0 may be selected before other resources slightly
|
44
|
+
# more often than otherwise.
|
45
|
+
|
46
|
+
# Tracks the total weight of all resources remaining in the available
|
47
|
+
# list.
|
48
|
+
total_weight = available.inject(0) { |sum, e| sum + e.weight + 1 }
|
49
|
+
|
50
|
+
until available.empty?
|
51
|
+
# Randomly select from the available list such that the probability of
|
52
|
+
# selecting a resource is proportional to the resource's weight.
|
53
|
+
selector = Integer(rand * total_weight) + 1
|
54
|
+
selected_idx = available.find_index do |e|
|
55
|
+
selector -= e.weight + 1
|
56
|
+
selector <= 0
|
57
|
+
end
|
58
|
+
selected = available.delete_at(selected_idx)
|
59
|
+
|
60
|
+
# Account for the removal of a resource from the available list.
|
61
|
+
total_weight -= selected.weight + 1
|
62
|
+
|
63
|
+
yield(selected)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/mock'
|
3
|
+
require 'minitest/spec'
|
4
|
+
|
5
|
+
require 'resolv-srv'
|
6
|
+
|
7
|
+
describe "each_srv_resource" do
|
8
|
+
before do
|
9
|
+
@dns = Resolv::DNS.new
|
10
|
+
domain = 'example.com'
|
11
|
+
hostname_template = "foo%i-%i-%i.#{domain}"
|
12
|
+
@prioritized_weights = [
|
13
|
+
[0, 0, 100, 400, 400],
|
14
|
+
[100],
|
15
|
+
[400, 400],
|
16
|
+
[1, 2, 3, 4, 5],
|
17
|
+
]
|
18
|
+
@srv_resources =
|
19
|
+
@prioritized_weights.
|
20
|
+
to_enum(:each_with_index).
|
21
|
+
flat_map do |weights, priority|
|
22
|
+
weights.each_with_index.map do |weight, n|
|
23
|
+
Resolv::DNS::Resource::IN::SRV.new(
|
24
|
+
priority, weight, 12345, hostname_template % [n, priority, weight]
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end.shuffle
|
28
|
+
end
|
29
|
+
|
30
|
+
after do
|
31
|
+
@dns.close
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'raises an exception if the service name is invalid' do
|
35
|
+
-> {
|
36
|
+
@dns.each_srv_resource('bad.service.name', 'tcp', 'example.com') {}
|
37
|
+
}.must_raise(ArgumentError)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'raises an exception if the service name is empty' do
|
41
|
+
-> {
|
42
|
+
@dns.each_srv_resource('', 'tcp', 'example.com') {}
|
43
|
+
}.must_raise(ArgumentError)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'raises an exception if the service name is nil' do
|
47
|
+
-> {
|
48
|
+
@dns.each_srv_resource(nil, 'tcp', 'example.com') {}
|
49
|
+
}.must_raise(ArgumentError)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'raises an exception if the protocol name is invalid' do
|
53
|
+
-> {
|
54
|
+
@dns.each_srv_resource('ldap', 'bad.protocol.name', 'example.com') {}
|
55
|
+
}.must_raise(ArgumentError)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'raises an exception if the protocol name is empty' do
|
59
|
+
-> {
|
60
|
+
@dns.each_srv_resource('ldap', '', 'example.com') {}
|
61
|
+
}.must_raise(ArgumentError)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'raises an exception if the protocol name is nil' do
|
65
|
+
-> {
|
66
|
+
@dns.each_srv_resource('ldap', nil, 'example.com') {}
|
67
|
+
}.must_raise(ArgumentError)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'orders by priority' do
|
71
|
+
@dns.stub(:getresources, @srv_resources) do
|
72
|
+
@dns.to_enum(:each_srv_resource, 'ldap', 'tcp', 'example.com').
|
73
|
+
map do |srv|
|
74
|
+
srv.priority
|
75
|
+
end.
|
76
|
+
must_equal(
|
77
|
+
@prioritized_weights.
|
78
|
+
to_enum(:each_with_index).
|
79
|
+
flat_map do |weights, priority|
|
80
|
+
[priority] * weights.size
|
81
|
+
end
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'orders randomly in proportion to weight' do
|
87
|
+
results = Hash.new do |h, priority|
|
88
|
+
h[priority] = Hash.new do |h2, srv|
|
89
|
+
h2[srv] = Array.new(@prioritized_weights[priority].size, 0)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
rounds = 100_000
|
94
|
+
@dns.stub(:getresources, @srv_resources) do
|
95
|
+
rounds.times do
|
96
|
+
selection = -1
|
97
|
+
old_priority = 0
|
98
|
+
@dns.each_srv_resource('ldap', 'tcp', 'example.com') do |srv|
|
99
|
+
if srv.priority != old_priority
|
100
|
+
old_priority = srv.priority
|
101
|
+
selection = 0
|
102
|
+
else
|
103
|
+
selection += 1
|
104
|
+
end
|
105
|
+
|
106
|
+
results[srv.priority][srv][selection] += 1
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
results.each do |priority, srv_selections|
|
112
|
+
srv_selections.each do |srv, selections|
|
113
|
+
selections.each_with_index do |hits, position|
|
114
|
+
expected = expected_probability(
|
115
|
+
position, srv.weight, @prioritized_weights[priority]
|
116
|
+
)
|
117
|
+
actual = Float(hits) / rounds
|
118
|
+
actual.must_be_close_to(expected, 0.02)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def expected_probability(position, selected_weight, weights)
|
125
|
+
return first_probability(selected_weight, weights) if position == 0
|
126
|
+
|
127
|
+
simple_remove(weights, selected_weight).inject(0) do |sum, w|
|
128
|
+
sum +
|
129
|
+
first_probability(w, weights) *
|
130
|
+
expected_probability(
|
131
|
+
position - 1, selected_weight, simple_remove(weights, w)
|
132
|
+
)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def first_probability(selected_weight, weights)
|
137
|
+
total_weight = weights.inject(0) { |sum, weight| sum + weight + 1 }
|
138
|
+
Float(selected_weight + 1) / total_weight
|
139
|
+
end
|
140
|
+
|
141
|
+
def simple_remove(array, element)
|
142
|
+
array = array.dup
|
143
|
+
array.delete_at(array.find_index(element))
|
144
|
+
array
|
145
|
+
end
|
146
|
+
end
|
metadata
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: resolv-srv
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jeremy Bopp
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '10.1'
|
20
|
+
- - ">"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 10.1.1
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '10.1'
|
30
|
+
- - ">"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 10.1.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: minitest
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '5.3'
|
40
|
+
- - ">"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 5.3.1
|
43
|
+
type: :development
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - "~>"
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '5.3'
|
50
|
+
- - ">"
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 5.3.1
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: yard
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - "~>"
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 0.8.7
|
60
|
+
- - ">"
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.8.7.3
|
63
|
+
type: :development
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - "~>"
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 0.8.7
|
70
|
+
- - ">"
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: 0.8.7.3
|
73
|
+
- !ruby/object:Gem::Dependency
|
74
|
+
name: redcarpet
|
75
|
+
requirement: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - "~>"
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '3.1'
|
80
|
+
- - ">"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.1.0
|
83
|
+
type: :development
|
84
|
+
prerelease: false
|
85
|
+
version_requirements: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '3.1'
|
90
|
+
- - ">"
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: 3.1.0
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: github-markup
|
95
|
+
requirement: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - "~>"
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '1.0'
|
100
|
+
- - ">"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: 1.0.2
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.0'
|
110
|
+
- - ">"
|
111
|
+
- !ruby/object:Gem::Version
|
112
|
+
version: 1.0.2
|
113
|
+
- !ruby/object:Gem::Dependency
|
114
|
+
name: pry
|
115
|
+
requirement: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - "~>"
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
type: :development
|
121
|
+
prerelease: false
|
122
|
+
version_requirements: !ruby/object:Gem::Requirement
|
123
|
+
requirements:
|
124
|
+
- - "~>"
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
127
|
+
description: |
|
128
|
+
This gem patches the Resolv::DNS class in stdlib to include a method to resolve
|
129
|
+
and iterate over SRV records according to their relative priorities and weights.
|
130
|
+
email:
|
131
|
+
- jeremy@bopp.net
|
132
|
+
executables: []
|
133
|
+
extensions: []
|
134
|
+
extra_rdoc_files: []
|
135
|
+
files:
|
136
|
+
- ".yardopts"
|
137
|
+
- LICENSE
|
138
|
+
- NEWS.md
|
139
|
+
- README.md
|
140
|
+
- Rakefile
|
141
|
+
- lib/resolv-srv.rb
|
142
|
+
- spec/each_srv_resource_spec.rb
|
143
|
+
homepage: https://github.com/javanthropus/resolv-srv
|
144
|
+
licenses:
|
145
|
+
- MIT
|
146
|
+
metadata: {}
|
147
|
+
post_install_message:
|
148
|
+
rdoc_options: []
|
149
|
+
require_paths:
|
150
|
+
- lib
|
151
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - ">="
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '0'
|
161
|
+
requirements: []
|
162
|
+
rubyforge_project:
|
163
|
+
rubygems_version: 2.4.6
|
164
|
+
signing_key:
|
165
|
+
specification_version: 4
|
166
|
+
summary: Resolve and iterate over SRV DNS records correctly.
|
167
|
+
test_files:
|
168
|
+
- spec/each_srv_resource_spec.rb
|