chef-resolver 0.9.1 → 0.9.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 +2 -0
- data/README.md +17 -1
- data/bin/chef-resolver +7 -12
- data/lib/chef/resolver/file_watcher.rb +45 -0
- data/lib/chef/resolver/version.rb +1 -1
- data/lib/chef/resolver_server.rb +86 -61
- data/spec/{knife → files}/test2_knife.rb +0 -0
- data/spec/{knife → files}/test_knife.rb +0 -0
- data/spec/resolver_server_spec.rb +68 -13
- metadata +8 -7
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,20 @@
|
|
1
1
|
chef-resolver
|
2
2
|
==================
|
3
3
|
|
4
|
-
A DNS resolver for Mac OS X that does role queries to resolve hostnames
|
4
|
+
A DNS resolver for Mac OS X that does role queries to resolve hostnames
|
5
|
+
|
6
|
+
### Installation
|
7
|
+
|
8
|
+
```
|
9
|
+
gem install chef-resolver
|
10
|
+
chef-resolver install
|
11
|
+
nano -w ~/.chef-resolver.yml
|
12
|
+
```
|
13
|
+
|
14
|
+
### Usage
|
15
|
+
|
16
|
+
```
|
17
|
+
ssh role-name-3.config.chef
|
18
|
+
ssh other-role.chef
|
19
|
+
ping another-role.config2.chef
|
20
|
+
```
|
data/bin/chef-resolver
CHANGED
@@ -24,7 +24,7 @@ if ARGV[0] == 'install'
|
|
24
24
|
f.write <<-DEFAULT_CONFIG
|
25
25
|
# # Access with role.config_name.chef
|
26
26
|
# config_name:
|
27
|
-
#
|
27
|
+
# knife_file: /full/path/to/knife.rb
|
28
28
|
# env: # optional
|
29
29
|
# CHEF_ENV: production
|
30
30
|
DEFAULT_CONFIG
|
@@ -66,11 +66,12 @@ port #{dns_port}
|
|
66
66
|
RESOLVER
|
67
67
|
resolver.close
|
68
68
|
begin
|
69
|
+
FileUtils.mkdir '/etc/resolver', :mode => 0755 unless File.exist?('/etc/resolver')
|
69
70
|
FileUtils.cp(resolver.path, '/etc/resolver/chef')
|
70
71
|
rescue
|
71
|
-
|
72
|
-
|
73
|
-
|
72
|
+
puts "Installing system configuration files as root"
|
73
|
+
`sudo mkdir -m 0755 /etc/resolver 2>/dev/null`
|
74
|
+
`sudo cp #{resolver.path} /etc/resolver/chef`
|
74
75
|
end
|
75
76
|
resolver.unlink
|
76
77
|
|
@@ -100,14 +101,8 @@ elsif ARGV[0] == 'uninstall'
|
|
100
101
|
exit 0
|
101
102
|
end
|
102
103
|
|
103
|
-
# Load configs
|
104
|
-
unless File.exist?(config_path)
|
105
|
-
raise "Could not find config file: #{config_path}"
|
106
|
-
end
|
107
|
-
config = YAML.load_file(config_path)
|
108
|
-
|
109
104
|
# Start server
|
110
105
|
Thread.abort_on_exception = true
|
111
|
-
dns = Chef::ResolverServer.new dns_port,
|
112
|
-
dns.
|
106
|
+
dns = Chef::ResolverServer.new dns_port, config_path, true
|
107
|
+
dns.start
|
113
108
|
Thread.stop
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class Chef
|
2
|
+
module Resolver
|
3
|
+
class FileWatcher
|
4
|
+
def initialize filenames
|
5
|
+
self.filenames = filenames
|
6
|
+
end
|
7
|
+
|
8
|
+
def filenames= filenames
|
9
|
+
@filenames = filenames || []
|
10
|
+
@mtimes = {}
|
11
|
+
@filenames.each do |f|
|
12
|
+
raise "File does not exist: #{f}" unless File.exist?(f)
|
13
|
+
@mtimes[f] = File.stat(f).mtime
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def watch &callback
|
18
|
+
@thread = Thread.new { watch_filenames &callback }
|
19
|
+
end
|
20
|
+
|
21
|
+
def stop
|
22
|
+
@thread.kill
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def watch_filenames &callback
|
27
|
+
loop do
|
28
|
+
sleep 1
|
29
|
+
|
30
|
+
@filenames.each do |f|
|
31
|
+
if File.exist?(f)
|
32
|
+
mtime = File.stat(f).mtime
|
33
|
+
next if @mtimes[f] == mtime
|
34
|
+
@mtimes[f] = mtime
|
35
|
+
yield f
|
36
|
+
else
|
37
|
+
yield f
|
38
|
+
next
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/chef/resolver_server.rb
CHANGED
@@ -1,46 +1,31 @@
|
|
1
1
|
require 'resolv'
|
2
2
|
require 'socket'
|
3
3
|
require 'chef'
|
4
|
+
require 'chef/resolver/file_watcher'
|
4
5
|
|
5
6
|
class Chef
|
6
7
|
class ResolverServer
|
7
|
-
|
8
|
-
@server = UDPSocket.open
|
9
|
-
@server.bind "127.0.0.1", port
|
10
|
-
|
11
|
-
domains = config.keys
|
12
|
-
@root_domains = {}
|
13
|
-
domains.each {|d| @root_domains[Resolv::DNS::Name.create("#{d}.chef.")] = config[d]}
|
14
|
-
@root_domains[Resolv::DNS::Name.create("chef.")] = config[domains[0]] if domains.length == 1
|
15
|
-
end
|
8
|
+
IPv4 = "127.0.0.1".freeze
|
16
9
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
else
|
25
|
-
return query_chef role_part, 0, config
|
26
|
-
end
|
10
|
+
def initialize port, config, watch = false
|
11
|
+
@port = port
|
12
|
+
if config.is_a?(String)
|
13
|
+
load_config_from_file config
|
14
|
+
start_file_watcher if watch
|
15
|
+
else
|
16
|
+
load_config config
|
27
17
|
end
|
28
|
-
return nil
|
29
18
|
end
|
30
19
|
|
31
|
-
def
|
32
|
-
|
20
|
+
def start
|
21
|
+
@server = UDPSocket.open
|
22
|
+
@server.bind IPv4, @port
|
23
|
+
@thread = Thread.new { process_requests }
|
24
|
+
end
|
33
25
|
|
34
|
-
|
35
|
-
|
36
|
-
if
|
37
|
-
puts "\tIndex beyond bounds: #{index} vs #{nodes.length}"
|
38
|
-
return nil
|
39
|
-
else
|
40
|
-
node = nodes[index]
|
41
|
-
puts "\tFound node: #{node['name']}"
|
42
|
-
return node.has_key?('ec2') ? node['ec2']['public_ipv4'] : node['ipaddress']
|
43
|
-
end
|
26
|
+
def stop
|
27
|
+
@thread.kill if @thread
|
28
|
+
@watcher.stop if @watcher
|
44
29
|
end
|
45
30
|
|
46
31
|
def load_chef_config config
|
@@ -65,47 +50,87 @@ class Chef
|
|
65
50
|
nil
|
66
51
|
end
|
67
52
|
|
68
|
-
|
69
|
-
|
70
|
-
|
53
|
+
private
|
54
|
+
def load_config config
|
55
|
+
domains = config.keys
|
56
|
+
@root_domains = {}
|
57
|
+
domains.each {|d| @root_domains[Resolv::DNS::Name.create("#{d}.chef.")] = config[d]}
|
58
|
+
@root_domains[Resolv::DNS::Name.create("chef.")] = config[domains[0]] if domains.length == 1
|
59
|
+
end
|
60
|
+
|
61
|
+
def load_config_from_file config_path
|
62
|
+
raise "Could not find config file: #{config_path}" unless File.exist?(config_path)
|
63
|
+
|
64
|
+
# Parse config from file and save paths to all files we might want to track
|
65
|
+
@config_path = File.expand_path(config_path)
|
66
|
+
@tracked_files = [@config_path]
|
67
|
+
load_config YAML.load_file(@config_path)
|
68
|
+
@root_domains.each {|d, c| @tracked_files << File.expand_path(c['knife_file'])}
|
71
69
|
end
|
72
70
|
|
73
|
-
def
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
a.rcode = 0
|
81
|
-
a
|
71
|
+
def start_file_watcher
|
72
|
+
@watcher = Resolver::FileWatcher.new @tracked_files
|
73
|
+
@watcher.watch do |path|
|
74
|
+
@cached_configs = nil # Reset chef configs
|
75
|
+
load_config_from_file @config_path if path == @config_path
|
76
|
+
@watcher.filenames = @tracked_files
|
77
|
+
end
|
82
78
|
end
|
83
79
|
|
84
|
-
def
|
85
|
-
|
80
|
+
def resolve_host host
|
81
|
+
puts "Resolving: #{host}"
|
82
|
+
@root_domains.each do |domain, config|
|
83
|
+
next unless host.subdomain_of?(domain)
|
84
|
+
next unless host.length - 1 == domain.length # Make sure the subdomain is the role part
|
85
|
+
role_part = host.to_s.split('.')[0]
|
86
|
+
if role_part =~ /^(.+)-(\d+)$/
|
87
|
+
return query_chef $1, $2.to_i - 1, config
|
88
|
+
else
|
89
|
+
return query_chef role_part, 0, config
|
90
|
+
end
|
91
|
+
end
|
92
|
+
return nil
|
86
93
|
end
|
87
94
|
|
88
|
-
def
|
89
|
-
|
90
|
-
while true
|
91
|
-
msg, from = read_msg
|
95
|
+
def query_chef role, index, config
|
96
|
+
load_chef_config config
|
92
97
|
|
93
|
-
|
98
|
+
puts "\tLooking up role #{role}..."
|
99
|
+
nodes = Chef::Search::Query.new.search('node', "role:#{role}")[0]
|
100
|
+
if index >= nodes.length
|
101
|
+
puts "\tIndex beyond bounds: #{index} vs #{nodes.length}"
|
102
|
+
return nil
|
103
|
+
else
|
104
|
+
node = nodes[index]
|
105
|
+
puts "\tFound node: #{node['name']}"
|
106
|
+
return node.has_key?('ec2') ? node['ec2']['public_ipv4'] : node['ipaddress']
|
107
|
+
end
|
108
|
+
end
|
94
109
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
110
|
+
def process_requests
|
111
|
+
loop do
|
112
|
+
data, from = @server.recvfrom(1024)
|
113
|
+
msg = Resolv::DNS::Message.decode(data)
|
114
|
+
|
115
|
+
a = Resolv::DNS::Message.new msg.id
|
116
|
+
a.qr = 1
|
117
|
+
a.opcode = msg.opcode
|
118
|
+
a.aa = 1
|
119
|
+
a.rd = msg.rd
|
120
|
+
|
121
|
+
msg.each_question do |q, cls|
|
122
|
+
next unless Resolv::DNS::Resource::IN::A == cls
|
123
|
+
ip = resolve_host q
|
124
|
+
if ip
|
125
|
+
a.add_answer "#{q.to_s}.", 60, cls.new(ip)
|
99
126
|
end
|
100
|
-
|
101
|
-
send_to a.encode, from
|
102
127
|
end
|
103
|
-
|
104
|
-
end
|
128
|
+
a.rcode = 3 unless a.answer.length > 0 # Not found
|
105
129
|
|
106
|
-
|
130
|
+
@server.send a.encode, 0, from[2], from[1]
|
131
|
+
end
|
132
|
+
ensure
|
107
133
|
@server.close
|
108
|
-
@thread.kill if @thread
|
109
134
|
end
|
110
135
|
end
|
111
136
|
end
|
File without changes
|
File without changes
|
@@ -5,9 +5,8 @@ require 'chef/resolver_server'
|
|
5
5
|
describe Chef::ResolverServer do
|
6
6
|
TESTING_DNS_PORT = 20571
|
7
7
|
|
8
|
-
def
|
9
|
-
@server = Chef::ResolverServer.new TESTING_DNS_PORT, config
|
10
|
-
@server.run
|
8
|
+
def new_server config, watch = false
|
9
|
+
@server = Chef::ResolverServer.new TESTING_DNS_PORT, config, watch
|
11
10
|
end
|
12
11
|
|
13
12
|
def stop_server
|
@@ -40,8 +39,8 @@ describe Chef::ResolverServer do
|
|
40
39
|
|
41
40
|
before :all do
|
42
41
|
@default_config = Chef::Config.configuration
|
43
|
-
@test_config = {'knife_file' => File.dirname(__FILE__)+'/
|
44
|
-
@test2_config = {'knife_file' => File.dirname(__FILE__)+'/
|
42
|
+
@test_config = {'knife_file' => File.dirname(__FILE__)+'/files/test_knife.rb'}
|
43
|
+
@test2_config = {'knife_file' => File.dirname(__FILE__)+'/files/test2_knife.rb', 'env' => {'ENV_PROP' => 'test2'}}
|
45
44
|
end
|
46
45
|
|
47
46
|
after :each do
|
@@ -50,13 +49,13 @@ describe Chef::ResolverServer do
|
|
50
49
|
end
|
51
50
|
|
52
51
|
it "should load chef config" do
|
53
|
-
|
52
|
+
new_server 'test' => @test_config
|
54
53
|
@server.load_chef_config @test_config
|
55
54
|
Chef::Config.test_prop.should == true
|
56
55
|
end
|
57
56
|
|
58
57
|
it "should properly reset chef config between loads" do
|
59
|
-
|
58
|
+
new_server 'test' => @test_config, 'test2' => @test2_config
|
60
59
|
@server.load_chef_config @test_config
|
61
60
|
Chef::Config.test_prop.should == true
|
62
61
|
Chef::Config.shared_prop.should == 'test_knife'
|
@@ -68,7 +67,7 @@ describe Chef::ResolverServer do
|
|
68
67
|
end
|
69
68
|
|
70
69
|
it "should properly reset ENV between chef config loads" do
|
71
|
-
|
70
|
+
new_server 'test' => @test_config, 'test2' => @test2_config
|
72
71
|
@server.load_chef_config @test_config
|
73
72
|
@server.load_chef_config @test2_config
|
74
73
|
Chef::Config.env_prop.should == 'test2'
|
@@ -78,32 +77,88 @@ describe Chef::ResolverServer do
|
|
78
77
|
end
|
79
78
|
|
80
79
|
it "should resolve a name like ROLE.CONFIG.chef" do
|
81
|
-
|
80
|
+
new_server 'test' => @test_config, 'test2' => @test2_config
|
81
|
+
@server.start
|
82
82
|
stub_search 'test_role', [{'ipaddress' => '1.1.1.1'}]
|
83
83
|
getaddress('test_role.test2.chef').should == '1.1.1.1'
|
84
84
|
end
|
85
85
|
|
86
86
|
it "should resolve a name like ROLE-INDEX.CONFIG.chef" do
|
87
|
-
|
87
|
+
new_server 'test' => @test_config
|
88
|
+
@server.start
|
88
89
|
stub_search 'test_role', [{'ipaddress' => '1.1.1.1'}, {'ipaddress' => '2.2.2.2'}, {'ipaddress' => '3.3.3.3'}, {'ipaddress' => '4.4.4.4'}]
|
89
90
|
getaddress('test_role-3.test.chef').should == '3.3.3.3'
|
90
91
|
end
|
91
92
|
|
92
93
|
it "should resolve a name like ROLE.chef if only one config" do
|
93
94
|
# Should fail with multiple configs
|
94
|
-
|
95
|
+
new_server 'test' => @test_config, 'test2' => @test2_config
|
96
|
+
@server.start
|
95
97
|
expect { getaddress('test_role.chef') }.to raise_error(Resolv::ResolvError)
|
96
98
|
stop_server
|
97
99
|
|
98
100
|
# Should succeed with one config
|
99
|
-
|
101
|
+
new_server 'test' => @test_config
|
102
|
+
@server.start
|
100
103
|
stub_search 'test_role', [{'ipaddress' => '1.1.1.1'}]
|
101
104
|
getaddress('test_role.chef').should == '1.1.1.1'
|
102
105
|
end
|
103
106
|
|
107
|
+
it "should not resolve ROLE.CONFIG.chef without a matching config" do
|
108
|
+
new_server 'test' => @test_config
|
109
|
+
@server.start
|
110
|
+
expect { getaddress('test_role.test2.chef') }.to raise_error(Resolv::ResolvError)
|
111
|
+
end
|
112
|
+
|
104
113
|
it "should resolve ec2 node public ip addresses properly" do
|
105
|
-
|
114
|
+
new_server 'test' => @test_config
|
115
|
+
@server.start
|
106
116
|
stub_search 'test_role', [{'ec2' => {'public_ipv4' => '1.1.1.1'}, 'ipaddress' => '0.0.0.0'}]
|
107
117
|
getaddress('test_role.chef').should == '1.1.1.1'
|
108
118
|
end
|
119
|
+
|
120
|
+
it "should support a file path" do
|
121
|
+
path = File.dirname(__FILE__)+'/files/changing_config.yml'
|
122
|
+
File.open(path, 'w') {|f| f.write({'test' => @test_config}.to_yaml)}
|
123
|
+
new_server path
|
124
|
+
@server.start
|
125
|
+
stub_search 'test_role', [{'ipaddress' => '1.1.1.1'}]
|
126
|
+
getaddress('test_role.chef').should == '1.1.1.1'
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should reload configs on config change" do
|
130
|
+
path = File.dirname(__FILE__)+'/files/changing_config.yml'
|
131
|
+
File.open(path, 'w') {|f| f.write({'test' => @test_config}.to_yaml)}
|
132
|
+
new_server path, true
|
133
|
+
@server.start
|
134
|
+
|
135
|
+
sleep 2
|
136
|
+
File.open(path, 'w') {|f| f.write({'test2' => @test2_config}.to_yaml)}
|
137
|
+
sleep 2
|
138
|
+
|
139
|
+
expect { getaddress('test_role.test.chef') }.to raise_error(Resolv::ResolvError)
|
140
|
+
stub_search 'test_role', [{'ipaddress' => '1.1.1.1'}]
|
141
|
+
getaddress('test_role.test2.chef').should == '1.1.1.1'
|
142
|
+
end
|
143
|
+
|
144
|
+
it "should reload configs on knife change" do
|
145
|
+
config_path = File.dirname(__FILE__)+'/files/changing_config.yml'
|
146
|
+
knife_path = File.dirname(__FILE__)+'/files/changing_knife.rb'
|
147
|
+
File.open(config_path, 'w') {|f| f.write({'changing' => {'knife_file' => knife_path}}.to_yaml)}
|
148
|
+
File.open(knife_path, 'w') {|f| f.write("test_prop true\n")}
|
149
|
+
new_server config_path, true
|
150
|
+
@server.start
|
151
|
+
|
152
|
+
stub_search 'test_role', [{'ipaddress' => '1.1.1.1'}]
|
153
|
+
getaddress('test_role.changing.chef').should == '1.1.1.1'
|
154
|
+
Chef::Config.test_prop.should == true
|
155
|
+
|
156
|
+
sleep 2
|
157
|
+
File.open(knife_path, 'w') {|f| f.write("test_prop false\n")}
|
158
|
+
sleep 2
|
159
|
+
|
160
|
+
stub_search 'test_role', [{'ipaddress' => '1.1.1.1'}]
|
161
|
+
getaddress('test_role.changing.chef').should == '1.1.1.1'
|
162
|
+
Chef::Config.test_prop.should == false
|
163
|
+
end
|
109
164
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 9
|
8
|
-
-
|
9
|
-
version: 0.9.
|
8
|
+
- 2
|
9
|
+
version: 0.9.2
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Stephen Augenstein
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2013-
|
17
|
+
date: 2013-02-03 00:00:00 -05:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -47,10 +47,11 @@ files:
|
|
47
47
|
- Rakefile
|
48
48
|
- bin/chef-resolver
|
49
49
|
- chef-resolver.gemspec
|
50
|
+
- lib/chef/resolver/file_watcher.rb
|
50
51
|
- lib/chef/resolver/version.rb
|
51
52
|
- lib/chef/resolver_server.rb
|
52
|
-
- spec/
|
53
|
-
- spec/
|
53
|
+
- spec/files/test2_knife.rb
|
54
|
+
- spec/files/test_knife.rb
|
54
55
|
- spec/resolver_server_spec.rb
|
55
56
|
- spec/spec_helper.rb
|
56
57
|
has_rdoc: true
|
@@ -84,7 +85,7 @@ signing_key:
|
|
84
85
|
specification_version: 3
|
85
86
|
summary: Instead of doing knife search node role every time you want to look up a server, simply ssh into ROLE_NAME-##.chef
|
86
87
|
test_files:
|
87
|
-
- spec/
|
88
|
-
- spec/
|
88
|
+
- spec/files/test2_knife.rb
|
89
|
+
- spec/files/test_knife.rb
|
89
90
|
- spec/resolver_server_spec.rb
|
90
91
|
- spec/spec_helper.rb
|