landrush 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|