landrush 0.3.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 +21 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +22 -0
- data/README.md +72 -0
- data/Rakefile +10 -0
- data/examples/Vagrantfile +26 -0
- data/landrush.gemspec +35 -0
- data/lib/ext/rexec.rb +10 -0
- data/lib/landrush.rb +27 -0
- data/lib/landrush/action/redirect_dns.rb +35 -0
- data/lib/landrush/action/setup.rb +42 -0
- data/lib/landrush/action/teardown.rb +46 -0
- data/lib/landrush/command.rb +45 -0
- data/lib/landrush/config.rb +32 -0
- data/lib/landrush/dependent_vms.rb +40 -0
- data/lib/landrush/plugin.rb +32 -0
- data/lib/landrush/resolver_config.rb +53 -0
- data/lib/landrush/server.rb +69 -0
- data/lib/landrush/store.rb +49 -0
- data/lib/landrush/util.rb +25 -0
- data/lib/landrush/version.rb +3 -0
- data/test/landrush/action/setup_test.rb +73 -0
- data/test/landrush/action/teardown_test.rb +92 -0
- data/test/landrush/dependent_vms_test.rb +33 -0
- data/test/landrush/resolver_config_test.rb +20 -0
- data/test/landrush/server_test.rb +22 -0
- data/test/landrush/store_test.rb +55 -0
- data/test/support/clear_dependent_vms.rb +10 -0
- data/test/support/fake_resolver_config.rb +21 -0
- data/test/support/fake_ui.rb +4 -0
- data/test/support/fake_working_dir.rb +16 -0
- data/test/support/test_server_daemon.rb +40 -0
- data/test/test_helper.rb +54 -0
- metadata +156 -0
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
#
|
4
|
+
# Keep track of dependent VMs.
|
5
|
+
#
|
6
|
+
# Poor man's race condition defense - touch and rm files in a directory and count them.
|
7
|
+
#
|
8
|
+
module Landrush
|
9
|
+
class DependentVMs
|
10
|
+
extend Enumerable
|
11
|
+
|
12
|
+
def self.each(&block)
|
13
|
+
(dir.directory? ? dir.children : []).each(&block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.add(machine)
|
17
|
+
FileUtils.touch(file_for(machine))
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.remove(machine)
|
21
|
+
file_for(machine).tap { |f| f.delete if f.exist? }
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.list
|
25
|
+
self.map { |path| path.basename.to_s }
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.clear!
|
29
|
+
dir.rmtree
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.file_for(machine)
|
33
|
+
dir.join(Util.hostname(machine))
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.dir
|
37
|
+
Landrush.working_dir.join('dependent_vms').tap(&:mkpath)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Landrush
|
2
|
+
class Plugin < Vagrant.plugin('2')
|
3
|
+
name 'landrush'
|
4
|
+
|
5
|
+
command 'landrush' do
|
6
|
+
require_relative 'command'
|
7
|
+
Command
|
8
|
+
end
|
9
|
+
|
10
|
+
config 'landrush' do
|
11
|
+
require_relative 'config'
|
12
|
+
Config
|
13
|
+
end
|
14
|
+
|
15
|
+
action_hook 'landrush_setup', :machine_action_up do |hook|
|
16
|
+
require_relative 'action/setup'
|
17
|
+
require_relative 'action/redirect_dns'
|
18
|
+
hook.before(VagrantPlugins::ProviderVirtualBox::Action::Boot, Action::Setup)
|
19
|
+
hook.after(VagrantPlugins::ProviderVirtualBox::Action::Boot, Action::RedirectDns)
|
20
|
+
end
|
21
|
+
|
22
|
+
action_hook 'landrush_teardown', :machine_action_halt do |hook|
|
23
|
+
require_relative 'action/teardown'
|
24
|
+
hook.after(Vagrant::Action::Builtin::GracefulHalt, Action::Teardown)
|
25
|
+
end
|
26
|
+
|
27
|
+
action_hook 'landrush_teardown', :machine_action_destroy do |hook|
|
28
|
+
require_relative 'action/teardown'
|
29
|
+
hook.after(Vagrant::Action::Builtin::GracefulHalt, Action::Teardown)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Landrush
|
4
|
+
class ResolverConfig
|
5
|
+
def self.desired_contents; <<-EOS.gsub(/^ /, '')
|
6
|
+
# Generated by landrush, a vagrant plugin
|
7
|
+
nameserver 127.0.0.1
|
8
|
+
port 10053
|
9
|
+
EOS
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.osx?
|
13
|
+
`uname`.chomp == 'Darwin'
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.config_file
|
17
|
+
@config_file ||= Pathname('/etc/resolver/vagrant.dev')
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.contents_match?
|
21
|
+
config_file.exist? && File.read(config_file) == desired_contents
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.write_config
|
25
|
+
puts "Mometarily using sudo to put the host config in place..."
|
26
|
+
Tempfile.open('vagrant_landrush_host_config') do |f|
|
27
|
+
f.write(desired_contents)
|
28
|
+
f.close
|
29
|
+
sudo = config_file.parent.writable? ? '' : 'sudo'
|
30
|
+
system "#{sudo} cp #{f.path} #{config_file}"
|
31
|
+
system "#{sudo} chmod 644 #{config_file}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.ensure_config_exists
|
36
|
+
unless osx?
|
37
|
+
puts "Not an OSX machine, so skipping host DNS resolver config."
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
if contents_match?
|
42
|
+
puts "Host DNS resolver config looks good."
|
43
|
+
else
|
44
|
+
puts "Need to configure the host."
|
45
|
+
write_config
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.puts(str)
|
50
|
+
Kernel.puts("[landrush] #{str}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'rubydns'
|
2
|
+
|
3
|
+
module Landrush
|
4
|
+
class Server < RExec::Daemon::Base
|
5
|
+
Name = Resolv::DNS::Name
|
6
|
+
IN = Resolv::DNS::Resource::IN
|
7
|
+
|
8
|
+
def self.port
|
9
|
+
@port ||= 10053
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.port=(port)
|
13
|
+
@port = port
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.upstream_servers
|
17
|
+
[[:udp, "8.8.8.8", 53], [:tcp, "8.8.8.8", 53]]
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.interfaces
|
21
|
+
[
|
22
|
+
[:udp, "0.0.0.0", port],
|
23
|
+
[:tcp, "0.0.0.0", port]
|
24
|
+
]
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.upstream
|
28
|
+
@upstream ||= landrush::Resolver.new(upstream_servers)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.pid
|
32
|
+
RExec::Daemon::ProcessFile.recall(self)
|
33
|
+
end
|
34
|
+
|
35
|
+
# For RExec
|
36
|
+
def self.working_directory
|
37
|
+
Landrush.working_dir
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.running?
|
41
|
+
RExec::Daemon::ProcessFile.status(self) == :running
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.prefork
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.run
|
49
|
+
server = self
|
50
|
+
RubyDNS::run_server(:listen => interfaces) do
|
51
|
+
self.logger.level = Logger::INFO
|
52
|
+
|
53
|
+
match(/.*/, IN::A) do |transaction|
|
54
|
+
ip = Store.hosts.get(transaction.name)
|
55
|
+
if ip
|
56
|
+
transaction.respond!(ip)
|
57
|
+
else
|
58
|
+
transaction.passthrough!(server.upstream)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Default DNS handler
|
63
|
+
otherwise do |transaction|
|
64
|
+
transaction.passthrough!(server.upstream)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Landrush
|
2
|
+
class Store
|
3
|
+
def self.hosts
|
4
|
+
@hosts ||= new(Landrush.working_dir.join('hosts.json'))
|
5
|
+
end
|
6
|
+
|
7
|
+
attr_accessor :backing_file
|
8
|
+
|
9
|
+
def initialize(backing_file)
|
10
|
+
@backing_file = Pathname(backing_file)
|
11
|
+
end
|
12
|
+
|
13
|
+
def set(key, value)
|
14
|
+
write(current_config.merge(key => value))
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(key)
|
18
|
+
write(current_config.reject { |k, _| k == key })
|
19
|
+
end
|
20
|
+
|
21
|
+
def get(key)
|
22
|
+
current_config[key]
|
23
|
+
end
|
24
|
+
|
25
|
+
def clear!
|
26
|
+
write({})
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def current_config
|
32
|
+
if backing_file.exist?
|
33
|
+
begin
|
34
|
+
JSON.parse(File.read(backing_file))
|
35
|
+
rescue JSON::ParserError
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
else
|
39
|
+
{}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def write(config)
|
44
|
+
File.open(backing_file, "w") do |f|
|
45
|
+
f.write(JSON.pretty_generate(config))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Landrush
|
2
|
+
module Util
|
3
|
+
def self.host_and_ip(machine)
|
4
|
+
[hostname(machine), ip_address(machine)]
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.hostname(machine)
|
8
|
+
return nil unless machine
|
9
|
+
|
10
|
+
machine.config.vm.hostname
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.ip_address(machine)
|
14
|
+
return nil unless machine
|
15
|
+
|
16
|
+
machine.config.vm.networks.each do |type, options|
|
17
|
+
if type == :private_network && options[:ip].is_a?(String)
|
18
|
+
return options[:ip]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'landrush/action/setup'
|
3
|
+
|
4
|
+
module Landrush
|
5
|
+
module Action
|
6
|
+
describe Setup do
|
7
|
+
it "calls the next app in the chain" do
|
8
|
+
env = fake_environment(called: false)
|
9
|
+
app = lambda { |e| e[:called] = true }
|
10
|
+
setup = Setup.new(app, nil)
|
11
|
+
|
12
|
+
setup.call(env)
|
13
|
+
|
14
|
+
env[:called].must_equal true
|
15
|
+
end
|
16
|
+
|
17
|
+
it "stores the machine's hostname => ip address" do
|
18
|
+
app = Proc.new {}
|
19
|
+
setup = Setup.new(app, nil)
|
20
|
+
env = fake_environment_with_machine('somehost.vagrant.dev', '1.2.3.4')
|
21
|
+
|
22
|
+
setup.call(env)
|
23
|
+
|
24
|
+
Store.hosts.get('somehost.vagrant.dev').must_equal '1.2.3.4'
|
25
|
+
end
|
26
|
+
|
27
|
+
it "records the booting host as a dependent VM" do
|
28
|
+
app = Proc.new {}
|
29
|
+
setup = Setup.new(app, nil)
|
30
|
+
env = fake_environment_with_machine('somehost.vagrant.dev', '1.2.3.4')
|
31
|
+
|
32
|
+
setup.call(env)
|
33
|
+
|
34
|
+
DependentVMs.list.must_equal %w[somehost.vagrant.dev]
|
35
|
+
end
|
36
|
+
|
37
|
+
it "starts the landrush server if it's not already started" do
|
38
|
+
app = Proc.new {}
|
39
|
+
setup = Setup.new(app, nil)
|
40
|
+
env = fake_environment_with_machine('somehost.vagrant.dev', '1.2.3.4')
|
41
|
+
|
42
|
+
setup.call(env)
|
43
|
+
|
44
|
+
Server.running?.must_equal true
|
45
|
+
end
|
46
|
+
|
47
|
+
it "does not attempt to start the server if it's already up" do
|
48
|
+
app = Proc.new {}
|
49
|
+
setup = Setup.new(app, nil)
|
50
|
+
env = fake_environment_with_machine('somehost.vagrant.dev', '1.2.3.4')
|
51
|
+
|
52
|
+
Server.start
|
53
|
+
original_pid = Server.pid
|
54
|
+
|
55
|
+
setup.call(env)
|
56
|
+
|
57
|
+
Server.running?.must_equal true
|
58
|
+
Server.pid.must_equal original_pid
|
59
|
+
end
|
60
|
+
|
61
|
+
it "does nothing if it is not enabled via config" do
|
62
|
+
app = Proc.new {}
|
63
|
+
setup = Setup.new(app, nil)
|
64
|
+
env = fake_environment_with_machine('somehost.vagrant.dev', '1.2.3.4')
|
65
|
+
|
66
|
+
env[:global_config].landrush.disable
|
67
|
+
setup.call(env)
|
68
|
+
|
69
|
+
Store.hosts.get('somehost.vagrant.dev').must_equal nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'landrush/action/teardown'
|
3
|
+
|
4
|
+
module Landrush
|
5
|
+
module Action
|
6
|
+
describe Teardown do
|
7
|
+
it "calls the next app in the chain" do
|
8
|
+
env = fake_environment(called: false)
|
9
|
+
app = lambda { |e| e[:called] = true }
|
10
|
+
teardown = Teardown.new(app, nil)
|
11
|
+
|
12
|
+
teardown.call(env)
|
13
|
+
|
14
|
+
env[:called].must_equal true
|
15
|
+
end
|
16
|
+
|
17
|
+
it "clears the machine's hostname => ip address" do
|
18
|
+
app = Proc.new {}
|
19
|
+
teardown = Teardown.new(app, nil)
|
20
|
+
env = fake_environment_with_machine('somehost.vagrant.dev', '1.2.3.4')
|
21
|
+
|
22
|
+
Store.hosts.set('somehost.vagrant.dev', '1.2.3.4')
|
23
|
+
teardown.call(env)
|
24
|
+
|
25
|
+
Store.hosts.get('somehost.vagrant.dev').must_equal nil
|
26
|
+
end
|
27
|
+
|
28
|
+
it "removes the machine as a dependent VM" do
|
29
|
+
app = Proc.new {}
|
30
|
+
teardown = Teardown.new(app, nil)
|
31
|
+
env = fake_environment_with_machine('somehost.vagrant.dev', '1.2.3.4')
|
32
|
+
|
33
|
+
DependentVMs.add(env[:machine])
|
34
|
+
teardown.call(env)
|
35
|
+
|
36
|
+
DependentVMs.list.must_equal []
|
37
|
+
end
|
38
|
+
|
39
|
+
it "stops the landrush server when there are no dependent machines left" do
|
40
|
+
app = Proc.new {}
|
41
|
+
teardown = Teardown.new(app, nil)
|
42
|
+
env = fake_environment_with_machine('somehost.vagrant.dev', '1.2.3.4')
|
43
|
+
|
44
|
+
Server.start
|
45
|
+
teardown.call(env)
|
46
|
+
|
47
|
+
Server.running?.must_equal false
|
48
|
+
end
|
49
|
+
|
50
|
+
it "leaves the landrush server when other dependent vms exist" do
|
51
|
+
app = Proc.new {}
|
52
|
+
teardown = Teardown.new(app, nil)
|
53
|
+
env = fake_environment_with_machine('somehost.vagrant.dev', '1.2.3.4')
|
54
|
+
|
55
|
+
other_env = fake_environment_with_machine('otherhost.vagrant.dev', '1.2.3.4')
|
56
|
+
DependentVMs.add(other_env[:machine])
|
57
|
+
|
58
|
+
Server.start
|
59
|
+
teardown.call(env)
|
60
|
+
|
61
|
+
Server.running?.must_equal true
|
62
|
+
end
|
63
|
+
|
64
|
+
it "leaves the server alone if it's not running" do
|
65
|
+
app = Proc.new {}
|
66
|
+
teardown = Teardown.new(app, nil)
|
67
|
+
env = fake_environment_with_machine('somehost.vagrant.dev', '1.2.3.4')
|
68
|
+
|
69
|
+
teardown.call(env)
|
70
|
+
|
71
|
+
Server.running?.must_equal false
|
72
|
+
end
|
73
|
+
|
74
|
+
it "does nothing when landrush is disabled" do
|
75
|
+
# somewhat unrealistic since this entry shouldn't be there if it was
|
76
|
+
# disabled in the first place, but oh well
|
77
|
+
Store.hosts.set('somehost.vagrant.dev', '1.2.3.4')
|
78
|
+
|
79
|
+
app = Proc.new {}
|
80
|
+
teardown = Teardown.new(app, nil)
|
81
|
+
|
82
|
+
env = fake_environment_with_machine('somehost.vagrant.dev', '1.2.3.4')
|
83
|
+
env[:global_config].landrush.disable
|
84
|
+
|
85
|
+
teardown.call(env)
|
86
|
+
|
87
|
+
Store.hosts.get('somehost.vagrant.dev').must_equal '1.2.3.4'
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|