invoker 1.4.1 → 1.5.1

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -1
  3. data/.rubocop.yml +0 -1
  4. data/.travis.yml +4 -2
  5. data/Dockerfile +7 -0
  6. data/Gemfile +3 -0
  7. data/Rakefile +8 -0
  8. data/examples/hello_sinatra.rb +26 -0
  9. data/examples/sample.ini +3 -0
  10. data/invoker.gemspec +2 -1
  11. data/lib/invoker.rb +20 -3
  12. data/lib/invoker/cli.rb +32 -6
  13. data/lib/invoker/dns_cache.rb +2 -2
  14. data/lib/invoker/ipc/add_http_command.rb +1 -1
  15. data/lib/invoker/ipc/dns_check_command.rb +2 -1
  16. data/lib/invoker/ipc/message.rb +2 -2
  17. data/lib/invoker/parsers/config.rb +4 -1
  18. data/lib/invoker/power/balancer.rb +16 -4
  19. data/lib/invoker/power/config.rb +3 -2
  20. data/lib/invoker/power/dns.rb +1 -1
  21. data/lib/invoker/power/pf_migrate.rb +1 -1
  22. data/lib/invoker/power/setup.rb +43 -4
  23. data/lib/invoker/power/setup/distro/arch.rb +1 -4
  24. data/lib/invoker/power/setup/distro/base.rb +23 -21
  25. data/lib/invoker/power/setup/distro/debian.rb +1 -1
  26. data/lib/invoker/power/setup/distro/opensuse.rb +11 -0
  27. data/lib/invoker/power/setup/distro/redhat.rb +1 -7
  28. data/lib/invoker/power/setup/files/invoker_forwarder.sh.erb +17 -0
  29. data/lib/invoker/power/setup/files/socat_invoker.service +12 -0
  30. data/lib/invoker/power/setup/linux_setup.rb +46 -29
  31. data/lib/invoker/power/setup/osx_setup.rb +16 -28
  32. data/lib/invoker/power/url_rewriter.rb +6 -3
  33. data/lib/invoker/process_manager.rb +13 -7
  34. data/lib/invoker/version.rb +1 -1
  35. data/readme.md +4 -4
  36. data/spec/invoker/commander_spec.rb +19 -8
  37. data/spec/invoker/config_spec.rb +22 -27
  38. data/spec/invoker/invoker_spec.rb +2 -1
  39. data/spec/invoker/ipc/client_handler_spec.rb +11 -1
  40. data/spec/invoker/power/config_spec.rb +2 -1
  41. data/spec/invoker/power/pf_migrate_spec.rb +7 -0
  42. data/spec/invoker/power/setup/linux_setup_spec.rb +57 -9
  43. data/spec/invoker/power/setup/osx_setup_spec.rb +22 -8
  44. data/spec/invoker/power/url_rewriter_spec.rb +33 -1
  45. data/spec/invoker/power/web_sockets_spec.rb +61 -0
  46. data/spec/invoker/process_manager_spec.rb +34 -2
  47. data/spec/spec_helper.rb +12 -16
  48. metadata +27 -35
  49. data/spec/support/mock_setup_file.rb +0 -64
@@ -3,14 +3,11 @@ module Invoker
3
3
  module Distro
4
4
  class Arch < Base
5
5
  def install_required_software
6
- system("pacman -S --needed --noconfirm dnsmasq")
6
+ system("pacman -S --needed --noconfirm dnsmasq socat")
7
7
  system("mkdir -p /etc/dnsmasq.d")
8
8
  unless File.open("/etc/dnsmasq.conf").each_line.any? { |line| line.chomp == "conf-dir=/etc/dnsmasq.d" }
9
9
  File.open("/etc/dnsmasq.conf", "a") {|f| f.write("conf-dir=/etc/dnsmasq.d") }
10
10
  end
11
- unless system("ls /usr/bin/rinetd > /dev/null 2>&1")
12
- fail "You'll need to install rinetd from the AUR in order to continue"
13
- end
14
11
  end
15
12
  end
16
13
  end
@@ -2,37 +2,42 @@ module Invoker
2
2
  module Power
3
3
  module Distro
4
4
  class Base
5
- RESOLVER_FILE = "/etc/dnsmasq.d/dev-tld"
6
- RINETD_FILE = "/etc/rinetd.conf"
5
+ SOCAT_SHELLSCRIPT = "/usr/bin/invoker_forwarder.sh"
6
+ SOCAT_SYSTEMD = "/etc/systemd/system/socat_invoker.service"
7
+ RESOLVER_DIR = "/etc/dnsmasq.d"
8
+ attr_accessor :tld
7
9
 
8
- def self.distro_installer
10
+ def resolver_file
11
+ File.join(RESOLVER_DIR, "#{tld}-tld")
12
+ end
13
+
14
+ def self.distro_installer(tld)
9
15
  case Facter[:operatingsystem].value
10
16
  when "Ubuntu"
11
17
  require "invoker/power/setup/distro/ubuntu"
12
- Ubuntu.new
18
+ Ubuntu.new(tld)
13
19
  when "Fedora"
14
20
  require "invoker/power/setup/distro/redhat"
15
- Redhat.new
21
+ Redhat.new(tld)
16
22
  when "Archlinux"
17
23
  require "invoker/power/setup/distro/arch"
18
- Arch.new
24
+ Arch.new(tld)
19
25
  when "Debian"
20
26
  require "invoker/power/setup/distro/debian"
21
- Debian.new
27
+ Debian.new(tld)
22
28
  when "LinuxMint"
23
29
  require "invoker/power/setup/distro/mint"
24
- Mint.new
30
+ Mint.new(tld)
31
+ when "OpenSuSE"
32
+ require "invoker/power/setup/distro/opensuse"
33
+ Opensuse.new(tld)
25
34
  else
26
35
  raise "Your selected distro is not supported by Invoker"
27
36
  end
28
37
  end
29
38
 
30
- def resolver_file
31
- RESOLVER_FILE
32
- end
33
-
34
- def rinetd_file
35
- RINETD_FILE
39
+ def initialize(tld)
40
+ self.tld = tld
36
41
  end
37
42
 
38
43
  # Install required software
@@ -41,13 +46,10 @@ module Invoker
41
46
  end
42
47
 
43
48
  def restart_services
44
- if Facter[:systemctl] == "true"
45
- system("systemctl restart rinetd")
46
- system("systemctl restart dnsmasq")
47
- else
48
- system("service rinetd restart")
49
- system("service dnsmasq restart")
50
- end
49
+ system("systemctl enable socat_invoker.service")
50
+ system("systemctl enable dnsmasq")
51
+ system("systemctl start socat_invoker.service")
52
+ system("systemctl restart dnsmasq")
51
53
  end
52
54
  end
53
55
  end
@@ -3,7 +3,7 @@ module Invoker
3
3
  module Distro
4
4
  class Debian < Base
5
5
  def install_required_software
6
- system("apt-get --assume-yes install dnsmasq rinetd")
6
+ system("apt-get --assume-yes install dnsmasq socat")
7
7
  end
8
8
  end
9
9
  end
@@ -0,0 +1,11 @@
1
+ module Invoker
2
+ module Power
3
+ module Distro
4
+ class Opensuse < Base
5
+ def install_required_software
6
+ system("zypper install -l dnsmasq socat")
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -3,13 +3,7 @@ module Invoker
3
3
  module Distro
4
4
  class Redhat < Base
5
5
  def install_required_software
6
- system("yum --assumeyes install dnsmasq rinetd")
7
- end
8
-
9
- def restart_services
10
- system("systemctl enable rinetd")
11
- system("service rinetd restart")
12
- system("service dnsmasq restart")
6
+ system("yum --assumeyes install dnsmasq socat")
13
7
  end
14
8
  end
15
9
  end
@@ -0,0 +1,17 @@
1
+ #!/bin/bash
2
+ set -e
3
+ KillJobs() {
4
+ for job in $(jobs -p); do
5
+ kill -s SIGTERM $job > /dev/null 2>&1 || (sleep 10 && kill -9 $job > /dev/null 2>&1 &)
6
+ done
7
+ }
8
+
9
+ # Whatever you need to clean here
10
+ trap KillJobs SIGINT SIGTERM EXIT
11
+
12
+ /usr/bin/socat TCP-LISTEN:80,reuseaddr,fork TCP:0.0.0.0:<%= http_port %>&
13
+ pid1=$!
14
+ /usr/bin/socat TCP-LISTEN:443,reuseaddr,fork TCP:0.0.0.0:<%= https_port %>&
15
+ pid2=$!
16
+ wait $pid1 $pid2
17
+ wait $pid1 $pid2
@@ -0,0 +1,12 @@
1
+ [Unit]
2
+ Description=Socat port forwarding service
3
+ After=network.target
4
+ Documentation=man:socat(1)
5
+
6
+ [Service]
7
+ ExecStart=/usr/bin/invoker_forwarder.sh
8
+ Restart=on-success
9
+
10
+
11
+ [Install]
12
+ WantedBy=multi-user.target
@@ -1,5 +1,7 @@
1
1
  require "invoker/power/setup/distro/base"
2
2
  require "facter"
3
+ require 'erb'
4
+ require 'fileutils'
3
5
 
4
6
  module Invoker
5
7
  module Power
@@ -22,17 +24,27 @@ module Invoker
22
24
  self
23
25
  end
24
26
 
25
- def create_config_file
26
- Invoker.setup_config_location
27
- Invoker::Power::Config.create(
28
- http_port: port_finder.http_port,
29
- https_port: port_finder.https_port
30
- )
27
+ def uninstall_invoker
28
+ system("systemctl disable socat_invoker.service")
29
+ system("systemctl stop socat_invoker.service")
30
+ system("rm #{Invoker::Power::Distro::Base::SOCAT_SYSTEMD}")
31
+ system("rm #{Invoker::Power::Distro::Base::SOCAT_SHELLSCRIPT}")
32
+ initialize_distro_installer
33
+ remove_resolver_file
34
+ drop_to_normal_user
35
+ Invoker::Power::Config.delete
31
36
  end
32
37
 
33
- def uninstall_invoker
34
- Invoker::Logger.puts("Uninstall is not yet supported on Linux."\
35
- " You can remove invoker changes by uninstalling dnsmasq and rinetd")
38
+ def resolver_file
39
+ distro_installer.resolver_file
40
+ end
41
+
42
+ def forwarder_script
43
+ File.join(File.dirname(__FILE__), "files/invoker_forwarder.sh.erb")
44
+ end
45
+
46
+ def socat_unit
47
+ File.join(File.dirname(__FILE__), "files/socat_invoker.service")
36
48
  end
37
49
 
38
50
  private
@@ -44,41 +56,46 @@ module Invoker
44
56
  Facter::Util::Resolution.exec("[ -e /usr/bin/systemctl ] && echo 'true' || echo 'false'")
45
57
  end
46
58
  end
47
- @distro_installer = Invoker::Power::Distro::Base.distro_installer
59
+ @distro_installer = Invoker::Power::Distro::Base.distro_installer(tld)
48
60
  end
49
61
 
50
62
  def install_resolver
51
- File.open(distro_installer.resolver_file, "w") do |fl|
52
- fl.write(tld_setup)
63
+ File.open(resolver_file, "w") do |fl|
64
+ fl.write(resolver_file_content)
53
65
  end
54
66
  end
55
67
 
56
68
  def install_port_forwarder
57
- File.open(distro_installer.rinetd_file, "a") do |fl|
58
- fl << "\n"
59
- fl << rinetd_setup(port_finder.http_port, port_finder.https_port)
60
- end
69
+ install_forwarder_script(port_finder.http_port, port_finder.https_port)
70
+ install_systemd_unit
61
71
  end
62
72
 
63
- def tld_setup
64
- tld_string =<<-EOD
65
- local=/dev/
66
- address=/dev/127.0.0.1
73
+ def resolver_file_content
74
+ content =<<-EOD
75
+ local=/#{tld}/
76
+ address=/#{tld}/127.0.0.1
67
77
  EOD
68
- tld_string
78
+ content
69
79
  end
70
80
 
71
- def rinetd_setup(http_port, https_port)
72
- rinetd_string =<<-EOD
73
- 0.0.0.0 80 0.0.0.0 #{http_port}
74
- 0.0.0.0 443 0.0.0.0 #{https_port}
75
- EOD
76
- rinetd_string
81
+ def install_forwarder_script(http_port, https_port)
82
+ script_template = File.read(forwarder_script)
83
+ renderer = ERB.new(script_template)
84
+ script_output = renderer.result(binding)
85
+ File.open(Invoker::Power::Distro::Base::SOCAT_SHELLSCRIPT, "w") do |fl|
86
+ fl.write(script_output)
87
+ end
88
+ system("chmod +x #{Invoker::Power::Distro::Base::SOCAT_SHELLSCRIPT}")
89
+ end
90
+
91
+ def install_systemd_unit
92
+ FileUtils.cp(socat_unit, Invoker::Power::Distro::Base::SOCAT_SYSTEMD)
93
+ system("chmod 644 #{Invoker::Power::Distro::Base::SOCAT_SYSTEMD}")
77
94
  end
78
95
 
79
96
  def get_user_confirmation?
80
- Invoker::Logger.puts("Invoker is going to install dnsmasq and rinetd on this machine."\
81
- " It is also going to install a local resolver for .dev domain and a rinetd rule"\
97
+ Invoker::Logger.puts("Invoker is going to install dnsmasq and socat on this machine."\
98
+ " It is also going to install a local resolver for .#{tld} domain and a socat service"\
82
99
  " which will forward all local requests on port 80 and 443 to another port")
83
100
  Invoker::Logger.puts("If you still want to proceed with installation, press y.")
84
101
  Invoker::CLI::Question.agree("Proceed with installation (y/n) : ")
@@ -1,9 +1,12 @@
1
1
  module Invoker
2
2
  module Power
3
3
  class OsxSetup < Setup
4
- RESOLVER_FILE = "/etc/resolver/dev"
5
- RESOLVER_DIR = "/etc/resolver"
6
4
  FIREWALL_PLIST_FILE = "/Library/LaunchDaemons/com.codemancers.invoker.firewall.plist"
5
+ RESOLVER_DIR = "/etc/resolver"
6
+
7
+ def resolver_file
8
+ File.join(RESOLVER_DIR, tld)
9
+ end
7
10
 
8
11
  def setup_invoker
9
12
  if setup_resolver_file
@@ -30,33 +33,19 @@ module Invoker
30
33
  end
31
34
  end
32
35
 
33
- def create_config_file
34
- Invoker.setup_config_location
35
- Invoker::Power::Config.create(
36
- dns_port: port_finder.dns_port,
37
- http_port: port_finder.http_port,
38
- https_port: port_finder.https_port
39
- )
36
+ def build_power_config
37
+ config = super
38
+ config[:dns_port] = port_finder.dns_port
39
+ config
40
40
  end
41
41
 
42
42
  def install_resolver(dns_port)
43
- open_resolver_for_write { |fl|
44
- fl.write(resolve_string(dns_port))
45
- }
43
+ open_resolver_for_write { |fl| fl.write(resolve_string(dns_port)) }
46
44
  rescue Errno::EACCES
47
45
  Invoker::Logger.puts("Running setup requires root access, please rerun it with sudo".color(:red))
48
46
  raise
49
47
  end
50
48
 
51
- def remove_resolver_file
52
- if File.exists?(RESOLVER_FILE)
53
- File.delete(RESOLVER_FILE)
54
- end
55
- rescue Errno::EACCES
56
- Invoker::Logger.puts("Running uninstall requires root access, please rerun it with sudo".color(:red))
57
- raise
58
- end
59
-
60
49
  def install_firewall(http_port, https_port)
61
50
  File.open(FIREWALL_PLIST_FILE, "w") { |fl|
62
51
  fl.write(plist_string(http_port, https_port))
@@ -118,19 +107,18 @@ port #{dns_port}
118
107
  end
119
108
 
120
109
  def setup_resolver_file
121
- return true unless File.exists?(RESOLVER_FILE)
110
+ return true unless File.exist?(resolver_file)
111
+
122
112
  Invoker::Logger.puts "Invoker has detected an existing Pow installation. We recommend "\
123
113
  "that you uninstall pow and rerun this setup.".color(:red)
124
-
125
114
  Invoker::Logger.puts "If you have already uninstalled Pow, proceed with installation"\
126
115
  " by pressing y/n."
127
-
128
116
  replace_resolver_flag = Invoker::CLI::Question.agree("Replace Pow configuration (y/n) : ")
129
117
 
130
118
  if replace_resolver_flag
131
119
  Invoker::Logger.puts "Invoker has overwritten one or more files created by Pow. "\
132
- "If .dev domains still don't resolve locally. Try turning off the wi-fi"\
133
- " and turning it on. It will force OSX to reload network configuration".color(:green)
120
+ "If .#{tld} domains still don't resolve locally, try turning off the wi-fi"\
121
+ " and turning it on. It'll force OS X to reload network configuration".color(:green)
134
122
  end
135
123
  replace_resolver_flag
136
124
  end
@@ -138,8 +126,8 @@ port #{dns_port}
138
126
  private
139
127
 
140
128
  def open_resolver_for_write
141
- FileUtils.mkdir(RESOLVER_DIR) unless Dir.exists?(RESOLVER_DIR)
142
- fl = File.open(RESOLVER_FILE, "w")
129
+ FileUtils.mkdir(RESOLVER_DIR) unless Dir.exist?(RESOLVER_DIR)
130
+ fl = File.open(resolver_file, "w")
143
131
  yield fl
144
132
  ensure
145
133
  fl && fl.close
@@ -1,8 +1,6 @@
1
1
  module Invoker
2
2
  module Power
3
3
  class UrlRewriter
4
- DEV_MATCH_REGEX = [/([\w.-]+)\.dev(\:\d+)?$/, /([\w-]+)\.dev(\:\d+)?$/]
5
-
6
4
  def select_backend_config(complete_path)
7
5
  possible_matches = extract_host_from_domain(complete_path)
8
6
  exact_match = nil
@@ -17,7 +15,7 @@ module Invoker
17
15
 
18
16
  def extract_host_from_domain(complete_path)
19
17
  matching_strings = []
20
- DEV_MATCH_REGEX.map do |regexp|
18
+ tld_match_regex.map do |regexp|
21
19
  if (match_result = complete_path.match(regexp))
22
20
  matching_strings << match_result[1]
23
21
  end
@@ -27,6 +25,11 @@ module Invoker
27
25
 
28
26
  private
29
27
 
28
+ def tld_match_regex
29
+ tld = Invoker.config.tld
30
+ [/([\w.-]+)\.#{tld}(\:\d+)?$/, /([\w-]+)\.#{tld}(\:\d+)?$/]
31
+ end
32
+
30
33
  def dns_check(dns_args)
31
34
  Invoker::IPC::UnixClient.send_command("dns_check", dns_args) do |dns_response|
32
35
  dns_response
@@ -89,17 +89,23 @@ module Invoker
89
89
  def load_env(directory = nil)
90
90
  directory ||= ENV['PWD']
91
91
  default_env = File.join(directory, '.env')
92
- return {} unless File.exist?(default_env)
93
- Dotenv::Environment.new(default_env)
92
+ local_env = File.join(directory, '.env.local')
93
+ env = {}
94
+
95
+ if File.exist?(default_env)
96
+ env.merge!(Dotenv::Environment.new(default_env))
97
+ end
98
+
99
+ if File.exist?(local_env)
100
+ env.merge!(Dotenv::Environment.new(local_env))
101
+ end
102
+
103
+ env
94
104
  end
95
105
 
96
106
  def kill_workers
97
107
  @workers.each do |key, worker|
98
- begin
99
- Process.kill("INT", -Process.getpgid(worker.pid))
100
- rescue Errno::ESRCH
101
- Invoker::Logger.puts "Error killing #{key}"
102
- end
108
+ kill_or_remove_process(worker.pid, "INT", worker.command_label)
103
109
  end
104
110
  @workers = {}
105
111
  end
@@ -43,5 +43,5 @@ module Invoker
43
43
  Version.new(next_splits.join('.'))
44
44
  end
45
45
  end
46
- VERSION = "1.4.1"
46
+ VERSION = "1.5.1"
47
47
  end
data/readme.md CHANGED
@@ -1,9 +1,9 @@
1
1
  Invoker is a gem for managing processes in development environment.
2
2
 
3
- [![Build Status](https://travis-ci.org/code-mancers/invoker.png)](https://travis-ci.org/code-mancers/invoker)
4
- [![Code Climate](https://codeclimate.com/github/code-mancers/invoker.png)](https://codeclimate.com/github/code-mancers/invoker)
5
- [![Coverage Status](https://coveralls.io/repos/code-mancers/invoker/badge.png)](https://coveralls.io/r/code-mancers/invoker)
6
- [![Dependency Status](https://gemnasium.com/code-mancers/invoker.png)](https://gemnasium.com/code-mancers/invoker)
3
+ [![Build Status](https://travis-ci.org/code-mancers/invoker.svg)](https://travis-ci.org/code-mancers/invoker)
4
+ [![Code Climate](https://codeclimate.com/github/code-mancers/invoker.svg)](https://codeclimate.com/github/code-mancers/invoker)
5
+ [![Coverage Status](https://coveralls.io/repos/code-mancers/invoker/badge.svg)](https://coveralls.io/r/code-mancers/invoker)
6
+ [![Dependency Status](https://gemnasium.com/code-mancers/invoker.svg)](https://gemnasium.com/code-mancers/invoker)
7
7
 
8
8
  ## Usage ##
9
9
 
@@ -1,13 +1,22 @@
1
1
  require "spec_helper"
2
2
 
3
3
  describe "Invoker::Commander" do
4
+ before(:each) do
5
+ @original_invoker_config = Invoker.config
6
+ Invoker.config = mock
7
+ end
8
+
9
+ after(:each) do
10
+ Invoker.config = @original_invoker_config
11
+ end
12
+
4
13
  describe "With no processes configured" do
5
- before do
14
+ before(:each) do
6
15
  @commander = Invoker::Commander.new
7
16
  end
8
17
 
9
18
  it "should throw error" do
10
- invoker_config.stubs(:processes).returns([])
19
+ Invoker.config.stubs(:processes).returns([])
11
20
 
12
21
  expect {
13
22
  @commander.start_manager
@@ -19,8 +28,9 @@ describe "Invoker::Commander" do
19
28
  describe "when not daemonized" do
20
29
  before do
21
30
  processes = [OpenStruct.new(:label => "foobar", :cmd => "foobar_command", :dir => ENV['HOME'])]
22
- invoker_config.stubs(:processes).returns(processes)
23
- invoker_config.stubs(:autorunnable_processes).returns(processes)
31
+ Invoker.config.stubs(:processes).returns(processes)
32
+ Invoker.config.stubs(:autorunnable_processes).returns(processes)
33
+ Invoker.stubs(:can_run_balancer?).returns(false)
24
34
  @commander = Invoker::Commander.new
25
35
  Invoker.commander = @commander
26
36
  end
@@ -52,8 +62,9 @@ describe "Invoker::Commander" do
52
62
  describe "when daemonized" do
53
63
  before do
54
64
  processes = [OpenStruct.new(:label => "foobar", :cmd => "foobar_command", :dir => ENV['HOME'])]
55
- invoker_config.stubs(:processes).returns(processes)
56
- invoker_config.stubs(:autorunnable_processes).returns(processes)
65
+ Invoker.config.stubs(:processes).returns(processes)
66
+ Invoker.config.stubs(:autorunnable_processes).returns(processes)
67
+ Invoker.stubs(:can_run_balancer?).returns(false)
57
68
  @commander = Invoker::Commander.new
58
69
  Invoker.commander = @commander
59
70
  Invoker.daemonize = true
@@ -94,8 +105,8 @@ describe "Invoker::Commander" do
94
105
  OpenStruct.new(:label => "foobar", :cmd => "foobar_command", :dir => ENV['HOME']),
95
106
  OpenStruct.new(:label => "panda", :cmd => "panda_command", :dir => ENV['HOME'], :disable_autorun => true)
96
107
  ]
97
- invoker_config.stubs(:processes).returns(@processes)
98
- invoker_config.stubs(:autorunnable_processes).returns([@processes.first])
108
+ Invoker.config.stubs(:processes).returns(@processes)
109
+ Invoker.config.stubs(:autorunnable_processes).returns([@processes.first])
99
110
 
100
111
  @commander = Invoker::Commander.new
101
112
  end