chef-resolver 0.9.0
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 +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
|