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.
@@ -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
+