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 CHANGED
@@ -16,3 +16,5 @@ tmp
16
16
  .yardoc
17
17
  _yardoc
18
18
  doc/
19
+
20
+ spec/files/changing_*
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
- # knife_path: /full/path/to/knife.rb
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
- cmd = "sudo cp #{resolver.path} /etc/resolver/chef"
72
- puts cmd
73
- `#{cmd}`
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, config
112
- dns.run
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
@@ -1,5 +1,5 @@
1
1
  class Chef
2
2
  module Resolver
3
- VERSION = "0.9.1"
3
+ VERSION = "0.9.2"
4
4
  end
5
5
  end
@@ -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
- def initialize(port, config)
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 resolve_host host
18
- puts "Resolving: #{host}"
19
- @root_domains.each do |domain, config|
20
- next unless host.subdomain_of?(domain)
21
- role_part = host.to_s.split('.')[0]
22
- if role_part =~ /^(.+)-(\d+)$/
23
- return query_chef $1, $2.to_i - 1, config
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 query_chef role, index, config
32
- load_chef_config config
20
+ def start
21
+ @server = UDPSocket.open
22
+ @server.bind IPv4, @port
23
+ @thread = Thread.new { process_requests }
24
+ end
33
25
 
34
- puts "\tLooking up role #{role}..."
35
- nodes = Chef::Search::Query.new.search('node', "role:#{role}")[0]
36
- if index >= nodes.length
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
- def read_msg
69
- data, from = @server.recvfrom(1024)
70
- return Resolv::DNS::Message.decode(data), from
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 answer(msg)
74
- a = Resolv::DNS::Message.new msg.id
75
- a.qr = 1
76
- a.opcode = msg.opcode
77
- a.aa = 1
78
- a.rd = msg.rd
79
- a.ra = 0
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 send_to(data, to)
85
- @server.send data, 0, to[2], to[1]
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 run
89
- @thread = Thread.new do
90
- while true
91
- msg, from = read_msg
95
+ def query_chef role, index, config
96
+ load_chef_config config
92
97
 
93
- a = answer(msg)
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
- msg.each_question do |q,cls|
96
- next unless Resolv::DNS::Resource::IN::A == cls
97
- ip = resolve_host q
98
- a.add_answer "#{q.to_s}.", 60, cls.new(ip) if ip
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
- end
104
- end
128
+ a.rcode = 3 unless a.answer.length > 0 # Not found
105
129
 
106
- def stop
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 start_server config
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__)+'/knife/test_knife.rb'}
44
- @test2_config = {'knife_file' => File.dirname(__FILE__)+'/knife/test2_knife.rb', 'env' => {'ENV_PROP' => 'test2'}}
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
- start_server 'test' => @test_config
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
- start_server 'test' => @test_config, 'test2' => @test2_config
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
- start_server 'test' => @test_config, 'test2' => @test2_config
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
- start_server 'test' => @test_config, 'test2' => @test2_config
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
- start_server 'test' => @test_config
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
- start_server 'test' => @test_config, 'test2' => @test2_config
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
- start_server 'test' => @test_config
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
- start_server 'test' => @test_config
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
- - 1
9
- version: 0.9.1
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-01-20 00:00:00 -05:00
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/knife/test2_knife.rb
53
- - spec/knife/test_knife.rb
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/knife/test2_knife.rb
88
- - spec/knife/test_knife.rb
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