chef-resolver 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in chef-resolver.gemspec
4
+ gemspec
5
+
6
+ gem 'rspec', '= 2.5'
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
@@ -0,0 +1,4 @@
1
+ chef-resolver
2
+ ==================
3
+
4
+ A DNS resolver for Mac OS X that does role queries to resolve hostnames
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
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,5 @@
1
+ class Chef
2
+ module Resolver
3
+ VERSION = "0.9.0"
4
+ end
5
+ 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,3 @@
1
+ test2_prop true
2
+ shared_prop 'test2_knife'
3
+ env_prop ENV['ENV_PROP']
@@ -0,0 +1,3 @@
1
+ test_prop true
2
+ shared_prop 'test_knife'
3
+ env_prop ENV['ENV_PROP']
@@ -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
@@ -0,0 +1,4 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'rspec'
4
+ require 'rspec/autorun'
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