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 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