landrush 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,3 @@
1
+ module Landrush
2
+ VERSION = "0.3.0"
3
+ 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
+