chef-resolver 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +4 -0
- data/Rakefile +4 -0
- data/bin/chef-resolver +113 -0
- data/chef-resolver.gemspec +21 -0
- data/lib/chef/resolver/version.rb +5 -0
- data/lib/chef/resolver_server.rb +111 -0
- data/spec/knife/test2_knife.rb +3 -0
- data/spec/knife/test_knife.rb +3 -0
- data/spec/resolver_server_spec.rb +109 -0
- data/spec/spec_helper.rb +4 -0
- metadata +90 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Stephen Augenstein
|
2
|
+
|
3
|
+
MIT License
|
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
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
data/bin/chef-resolver
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'chef-resolver/server'
|
7
|
+
|
8
|
+
config_path = "#{ENV['HOME']}/.chef-resolver.yml"
|
9
|
+
launch_agent_path = "#{ENV['HOME']}/Library/LaunchAgents/com.chromedshark.chef-resolver.plist"
|
10
|
+
dns_port = 20570
|
11
|
+
|
12
|
+
# Fail install/uninstall if we're not on OS X >= 10.6.0
|
13
|
+
if ARGV[0] == 'install' || ARGV[0] == 'uninstall'
|
14
|
+
die 'chef-resolver requires OS X' unless RUBY_PLATFORM.downcase.include?('darwin')
|
15
|
+
vers = `sw_vers -productVersion`.split('.').map {|p| p.to_i}
|
16
|
+
die 'chef-resolver requires OS X 10.6 or greater' unless vers[1] >= 6
|
17
|
+
end
|
18
|
+
|
19
|
+
# Install/Uninstall
|
20
|
+
if ARGV[0] == 'install'
|
21
|
+
# Install default config file
|
22
|
+
unless File.exist?(config_path)
|
23
|
+
File.open(config_path, 'w') do |f|
|
24
|
+
f.write <<-DEFAULT_CONFIG
|
25
|
+
# # Access with role.config_name.chef
|
26
|
+
# config_name:
|
27
|
+
# knife_path: /full/path/to/knife.rb
|
28
|
+
# env: # optional
|
29
|
+
# CHEF_ENV: production
|
30
|
+
DEFAULT_CONFIG
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Install launchd script for dns server
|
35
|
+
File.open(launch_agent_path, 'w') do |f|
|
36
|
+
f.write <<-LAUNCH_PLIST
|
37
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
38
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
39
|
+
<plist version="1.0">
|
40
|
+
<dict>
|
41
|
+
<key>Label</key>
|
42
|
+
<string>com.chromedshark.chef-resolver</string>
|
43
|
+
<key>ProgramArguments</key>
|
44
|
+
<array>
|
45
|
+
<string>chef-resolver</string>
|
46
|
+
</array>
|
47
|
+
<key>KeepAlive</key>
|
48
|
+
<true/>
|
49
|
+
<key>RunAtLoad</key>
|
50
|
+
<true/>
|
51
|
+
</dict>
|
52
|
+
</plist>
|
53
|
+
LAUNCH_PLIST
|
54
|
+
end
|
55
|
+
|
56
|
+
# Start server
|
57
|
+
`launchctl unload "#{launch_agent_path}" 2>/dev/null`
|
58
|
+
`launchctl load -Fw "#{launch_agent_path}" 2>/dev/null`
|
59
|
+
|
60
|
+
# Install resolver
|
61
|
+
resolver = Tempfile.new('chef')
|
62
|
+
resolver.write <<-RESOLVER
|
63
|
+
# Generated by chef-resolver
|
64
|
+
nameserver 127.0.0.1
|
65
|
+
port #{dns_port}
|
66
|
+
RESOLVER
|
67
|
+
resolver.close
|
68
|
+
begin
|
69
|
+
FileUtils.cp(resolver.path, '/etc/resolver/chef')
|
70
|
+
rescue
|
71
|
+
cmd = "sudo cp #{resolver.path} /etc/resolver/chef"
|
72
|
+
puts cmd
|
73
|
+
`#{cmd}`
|
74
|
+
end
|
75
|
+
resolver.unlink
|
76
|
+
|
77
|
+
puts <<-MSG
|
78
|
+
chef-resolver installed
|
79
|
+
|
80
|
+
Check out #{config_path} for instructions on configuring
|
81
|
+
MSG
|
82
|
+
exit 0
|
83
|
+
elsif ARGV[0] == 'uninstall'
|
84
|
+
# Uninstall server
|
85
|
+
`launchctl unload "#{launch_agent_path}" 2>/dev/null`
|
86
|
+
FileUtils.rm launch_agent_path, :force => true
|
87
|
+
|
88
|
+
# Remove resolver
|
89
|
+
if File.exist?('/etc/resolver/chef')
|
90
|
+
begin
|
91
|
+
FileUtils.rm('/etc/resolver/chef')
|
92
|
+
rescue
|
93
|
+
cmd = 'sudo rm /etc/resolver/chef'
|
94
|
+
puts cmd
|
95
|
+
`#{cmd}`
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
puts "chef-resolver uninstalled"
|
100
|
+
exit 0
|
101
|
+
end
|
102
|
+
|
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
|
+
# Start server
|
110
|
+
Thread.abort_on_exception = true
|
111
|
+
dns = Chef::ResolverServer.new dns_port, config
|
112
|
+
dns.run
|
113
|
+
Thread.stop
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'chef/resolver/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "chef-resolver"
|
8
|
+
gem.version = Chef::Resolver::VERSION
|
9
|
+
gem.authors = ["Stephen Augenstein"]
|
10
|
+
gem.email = ["perl.programmer@gmail.com"]
|
11
|
+
gem.description = %q{A DNS resolver for Mac OS X that does role queries to resolve hostnames}
|
12
|
+
gem.summary = %q{Instead of doing knife search node role every time you want to look up a server, simply ssh into ROLE_NAME-##.chef}
|
13
|
+
gem.homepage = "https://github.com/warhammerkid/chef-resolver"
|
14
|
+
|
15
|
+
gem.add_dependency('chef', '~> 10.16')
|
16
|
+
|
17
|
+
gem.files = `git ls-files`.split($/)
|
18
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
19
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
20
|
+
gem.require_paths = ["lib"]
|
21
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
require 'socket'
|
3
|
+
require 'chef'
|
4
|
+
|
5
|
+
class Chef
|
6
|
+
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
|
16
|
+
|
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
|
27
|
+
end
|
28
|
+
return nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def query_chef role, index, config
|
32
|
+
load_chef_config config
|
33
|
+
|
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
|
44
|
+
end
|
45
|
+
|
46
|
+
def load_chef_config config
|
47
|
+
@default_config ||= Chef::Config.configuration
|
48
|
+
@original_env ||= ENV.to_hash
|
49
|
+
@cached_configs ||= {}
|
50
|
+
|
51
|
+
cache_key = config.object_id
|
52
|
+
cached_config = @cached_configs[cache_key]
|
53
|
+
if cached_config
|
54
|
+
Chef::Config.configuration = cached_config
|
55
|
+
else
|
56
|
+
Chef::Config.configuration = @default_config.dup
|
57
|
+
(config['env'] || []).each do |key, val|
|
58
|
+
ENV[key] = val
|
59
|
+
end
|
60
|
+
Chef::Config.from_file(config['knife_file'])
|
61
|
+
@cached_configs[cache_key] = Chef::Config.configuration
|
62
|
+
ENV.replace(@original_env)
|
63
|
+
end
|
64
|
+
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def read_msg
|
69
|
+
data, from = @server.recvfrom(1024)
|
70
|
+
return Resolv::DNS::Message.decode(data), from
|
71
|
+
end
|
72
|
+
|
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
|
82
|
+
end
|
83
|
+
|
84
|
+
def send_to(data, to)
|
85
|
+
@server.send data, 0, to[2], to[1]
|
86
|
+
end
|
87
|
+
|
88
|
+
def run
|
89
|
+
@thread = Thread.new do
|
90
|
+
while true
|
91
|
+
msg, from = read_msg
|
92
|
+
|
93
|
+
a = answer(msg)
|
94
|
+
|
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
|
99
|
+
end
|
100
|
+
|
101
|
+
send_to a.encode, from
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def stop
|
107
|
+
@server.close
|
108
|
+
@thread.kill if @thread
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper.rb'
|
2
|
+
require 'resolv'
|
3
|
+
require 'chef/resolver_server'
|
4
|
+
|
5
|
+
describe Chef::ResolverServer do
|
6
|
+
TESTING_DNS_PORT = 20571
|
7
|
+
|
8
|
+
def start_server config
|
9
|
+
@server = Chef::ResolverServer.new TESTING_DNS_PORT, config
|
10
|
+
@server.run
|
11
|
+
end
|
12
|
+
|
13
|
+
def stop_server
|
14
|
+
@server.stop if @server
|
15
|
+
@server = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def stub_search role, nodes
|
19
|
+
query = double('Chef::Search::Query')
|
20
|
+
nodes.each_with_index {|n, i| n['name'] = "node #{i}" }
|
21
|
+
query.should_receive(:search).with('node', "role:#{role}").and_return([nodes, 0, nodes.length])
|
22
|
+
Chef::Search::Query.should_receive(:new) { query }
|
23
|
+
end
|
24
|
+
|
25
|
+
def getaddress host
|
26
|
+
unless @resolver
|
27
|
+
klass = Class.new(Resolv::DNS::Config) do
|
28
|
+
def nameserver_port
|
29
|
+
lazy_initialize
|
30
|
+
@nameserver_port ||= [['127.0.0.1', TESTING_DNS_PORT]]
|
31
|
+
@nameserver_port
|
32
|
+
end
|
33
|
+
end
|
34
|
+
config = klass.new(:nameserver => ['127.0.0.1'])
|
35
|
+
@resolver = Resolv::DNS.new
|
36
|
+
@resolver.instance_variable_set('@config', config)
|
37
|
+
end
|
38
|
+
return @resolver.getaddress(host).to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
before :all do
|
42
|
+
@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'}}
|
45
|
+
end
|
46
|
+
|
47
|
+
after :each do
|
48
|
+
stop_server
|
49
|
+
Chef::Config.configuration = @default_config.dup
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should load chef config" do
|
53
|
+
start_server 'test' => @test_config
|
54
|
+
@server.load_chef_config @test_config
|
55
|
+
Chef::Config.test_prop.should == true
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should properly reset chef config between loads" do
|
59
|
+
start_server 'test' => @test_config, 'test2' => @test2_config
|
60
|
+
@server.load_chef_config @test_config
|
61
|
+
Chef::Config.test_prop.should == true
|
62
|
+
Chef::Config.shared_prop.should == 'test_knife'
|
63
|
+
|
64
|
+
@server.load_chef_config @test2_config
|
65
|
+
Chef::Config.test_prop.should be_nil
|
66
|
+
Chef::Config.test2_prop.should == true
|
67
|
+
Chef::Config.shared_prop.should == 'test2_knife'
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should properly reset ENV between chef config loads" do
|
71
|
+
start_server 'test' => @test_config, 'test2' => @test2_config
|
72
|
+
@server.load_chef_config @test_config
|
73
|
+
@server.load_chef_config @test2_config
|
74
|
+
Chef::Config.env_prop.should == 'test2'
|
75
|
+
|
76
|
+
@server.load_chef_config @test_config
|
77
|
+
Chef::Config.env_prop.should be_nil
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should resolve a name like ROLE.CONFIG.chef" do
|
81
|
+
start_server 'test' => @test_config, 'test2' => @test2_config
|
82
|
+
stub_search 'test_role', [{'ipaddress' => '1.1.1.1'}]
|
83
|
+
getaddress('test_role.test2.chef').should == '1.1.1.1'
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should resolve a name like ROLE-INDEX.CONFIG.chef" do
|
87
|
+
start_server 'test' => @test_config
|
88
|
+
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
|
+
getaddress('test_role-3.test.chef').should == '3.3.3.3'
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should resolve a name like ROLE.chef if only one config" do
|
93
|
+
# Should fail with multiple configs
|
94
|
+
start_server 'test' => @test_config, 'test2' => @test2_config
|
95
|
+
expect { getaddress('test_role.chef') }.to raise_error(Resolv::ResolvError)
|
96
|
+
stop_server
|
97
|
+
|
98
|
+
# Should succeed with one config
|
99
|
+
start_server 'test' => @test_config
|
100
|
+
stub_search 'test_role', [{'ipaddress' => '1.1.1.1'}]
|
101
|
+
getaddress('test_role.chef').should == '1.1.1.1'
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should resolve ec2 node public ip addresses properly" do
|
105
|
+
start_server 'test' => @test_config
|
106
|
+
stub_search 'test_role', [{'ec2' => {'public_ipv4' => '1.1.1.1'}, 'ipaddress' => '0.0.0.0'}]
|
107
|
+
getaddress('test_role.chef').should == '1.1.1.1'
|
108
|
+
end
|
109
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: chef-resolver
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 9
|
8
|
+
- 0
|
9
|
+
version: 0.9.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Stephen Augenstein
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2013-01-20 00:00:00 -05:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
type: :runtime
|
22
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
segments:
|
27
|
+
- 10
|
28
|
+
- 16
|
29
|
+
version: "10.16"
|
30
|
+
name: chef
|
31
|
+
requirement: *id001
|
32
|
+
prerelease: false
|
33
|
+
description: A DNS resolver for Mac OS X that does role queries to resolve hostnames
|
34
|
+
email:
|
35
|
+
- perl.programmer@gmail.com
|
36
|
+
executables:
|
37
|
+
- chef-resolver
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files: []
|
41
|
+
|
42
|
+
files:
|
43
|
+
- .gitignore
|
44
|
+
- Gemfile
|
45
|
+
- LICENSE.txt
|
46
|
+
- README.md
|
47
|
+
- Rakefile
|
48
|
+
- bin/chef-resolver
|
49
|
+
- chef-resolver.gemspec
|
50
|
+
- lib/chef/resolver/version.rb
|
51
|
+
- lib/chef/resolver_server.rb
|
52
|
+
- spec/knife/test2_knife.rb
|
53
|
+
- spec/knife/test_knife.rb
|
54
|
+
- spec/resolver_server_spec.rb
|
55
|
+
- spec/spec_helper.rb
|
56
|
+
has_rdoc: true
|
57
|
+
homepage: https://github.com/warhammerkid/chef-resolver
|
58
|
+
licenses: []
|
59
|
+
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
segments:
|
70
|
+
- 0
|
71
|
+
version: "0"
|
72
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
segments:
|
77
|
+
- 0
|
78
|
+
version: "0"
|
79
|
+
requirements: []
|
80
|
+
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 1.3.6
|
83
|
+
signing_key:
|
84
|
+
specification_version: 3
|
85
|
+
summary: Instead of doing knife search node role every time you want to look up a server, simply ssh into ROLE_NAME-##.chef
|
86
|
+
test_files:
|
87
|
+
- spec/knife/test2_knife.rb
|
88
|
+
- spec/knife/test_knife.rb
|
89
|
+
- spec/resolver_server_spec.rb
|
90
|
+
- spec/spec_helper.rb
|