chef-resolver 0.9.1 → 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
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