meepo 1.5.2

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 (108) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +16 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +29 -0
  6. data/.travis.yml +10 -0
  7. data/Dockerfile +7 -0
  8. data/Gemfile +13 -0
  9. data/MIT-LICENSE +20 -0
  10. data/Rakefile +15 -0
  11. data/TODO +5 -0
  12. data/bin/invoker +7 -0
  13. data/contrib/completion/invoker-completion.bash +70 -0
  14. data/contrib/completion/invoker-completion.zsh +62 -0
  15. data/examples/hello_sinatra.rb +26 -0
  16. data/examples/sample.ini +3 -0
  17. data/invoker.gemspec +43 -0
  18. data/lib/invoker.rb +152 -0
  19. data/lib/invoker/cli.rb +159 -0
  20. data/lib/invoker/cli/pinger.rb +23 -0
  21. data/lib/invoker/cli/question.rb +15 -0
  22. data/lib/invoker/cli/tail.rb +34 -0
  23. data/lib/invoker/cli/tail_watcher.rb +34 -0
  24. data/lib/invoker/command_worker.rb +60 -0
  25. data/lib/invoker/commander.rb +95 -0
  26. data/lib/invoker/daemon.rb +126 -0
  27. data/lib/invoker/dns_cache.rb +23 -0
  28. data/lib/invoker/errors.rb +17 -0
  29. data/lib/invoker/event/manager.rb +79 -0
  30. data/lib/invoker/ipc.rb +45 -0
  31. data/lib/invoker/ipc/add_command.rb +12 -0
  32. data/lib/invoker/ipc/add_http_command.rb +10 -0
  33. data/lib/invoker/ipc/base_command.rb +24 -0
  34. data/lib/invoker/ipc/client_handler.rb +26 -0
  35. data/lib/invoker/ipc/dns_check_command.rb +17 -0
  36. data/lib/invoker/ipc/list_command.rb +11 -0
  37. data/lib/invoker/ipc/message.rb +170 -0
  38. data/lib/invoker/ipc/message/list_response.rb +35 -0
  39. data/lib/invoker/ipc/message/tail_response.rb +10 -0
  40. data/lib/invoker/ipc/ping_command.rb +10 -0
  41. data/lib/invoker/ipc/reload_command.rb +12 -0
  42. data/lib/invoker/ipc/remove_command.rb +12 -0
  43. data/lib/invoker/ipc/server.rb +26 -0
  44. data/lib/invoker/ipc/tail_command.rb +11 -0
  45. data/lib/invoker/ipc/unix_client.rb +60 -0
  46. data/lib/invoker/logger.rb +13 -0
  47. data/lib/invoker/parsers/config.rb +184 -0
  48. data/lib/invoker/parsers/procfile.rb +86 -0
  49. data/lib/invoker/power/balancer.rb +131 -0
  50. data/lib/invoker/power/config.rb +77 -0
  51. data/lib/invoker/power/dns.rb +38 -0
  52. data/lib/invoker/power/http_parser.rb +68 -0
  53. data/lib/invoker/power/http_response.rb +81 -0
  54. data/lib/invoker/power/pf_migrate.rb +64 -0
  55. data/lib/invoker/power/port_finder.rb +49 -0
  56. data/lib/invoker/power/power.rb +3 -0
  57. data/lib/invoker/power/powerup.rb +29 -0
  58. data/lib/invoker/power/setup.rb +90 -0
  59. data/lib/invoker/power/setup/distro/arch.rb +15 -0
  60. data/lib/invoker/power/setup/distro/base.rb +57 -0
  61. data/lib/invoker/power/setup/distro/debian.rb +11 -0
  62. data/lib/invoker/power/setup/distro/mint.rb +10 -0
  63. data/lib/invoker/power/setup/distro/opensuse.rb +11 -0
  64. data/lib/invoker/power/setup/distro/redhat.rb +11 -0
  65. data/lib/invoker/power/setup/distro/ubuntu.rb +10 -0
  66. data/lib/invoker/power/setup/files/invoker_forwarder.sh.erb +17 -0
  67. data/lib/invoker/power/setup/files/socat_invoker.service +12 -0
  68. data/lib/invoker/power/setup/linux_setup.rb +105 -0
  69. data/lib/invoker/power/setup/osx_setup.rb +137 -0
  70. data/lib/invoker/power/templates/400.html +40 -0
  71. data/lib/invoker/power/templates/404.html +40 -0
  72. data/lib/invoker/power/templates/503.html +40 -0
  73. data/lib/invoker/power/url_rewriter.rb +40 -0
  74. data/lib/invoker/process_manager.rb +198 -0
  75. data/lib/invoker/process_printer.rb +43 -0
  76. data/lib/invoker/reactor.rb +37 -0
  77. data/lib/invoker/reactor/reader.rb +54 -0
  78. data/lib/invoker/version.rb +47 -0
  79. data/readme.md +25 -0
  80. data/spec/invoker/cli/pinger_spec.rb +22 -0
  81. data/spec/invoker/cli/tail_watcher_spec.rb +39 -0
  82. data/spec/invoker/cli_spec.rb +27 -0
  83. data/spec/invoker/command_worker_spec.rb +45 -0
  84. data/spec/invoker/commander_spec.rb +152 -0
  85. data/spec/invoker/config_spec.rb +361 -0
  86. data/spec/invoker/daemon_spec.rb +34 -0
  87. data/spec/invoker/event/manager_spec.rb +67 -0
  88. data/spec/invoker/invoker_spec.rb +71 -0
  89. data/spec/invoker/ipc/client_handler_spec.rb +54 -0
  90. data/spec/invoker/ipc/dns_check_command_spec.rb +32 -0
  91. data/spec/invoker/ipc/message/list_response_spec.rb +24 -0
  92. data/spec/invoker/ipc/message_spec.rb +49 -0
  93. data/spec/invoker/ipc/unix_client_spec.rb +29 -0
  94. data/spec/invoker/power/balancer_spec.rb +22 -0
  95. data/spec/invoker/power/config_spec.rb +18 -0
  96. data/spec/invoker/power/http_parser_spec.rb +32 -0
  97. data/spec/invoker/power/http_response_spec.rb +34 -0
  98. data/spec/invoker/power/pf_migrate_spec.rb +87 -0
  99. data/spec/invoker/power/port_finder_spec.rb +16 -0
  100. data/spec/invoker/power/setup/linux_setup_spec.rb +103 -0
  101. data/spec/invoker/power/setup/osx_setup_spec.rb +105 -0
  102. data/spec/invoker/power/setup_spec.rb +4 -0
  103. data/spec/invoker/power/url_rewriter_spec.rb +70 -0
  104. data/spec/invoker/power/web_sockets_spec.rb +61 -0
  105. data/spec/invoker/process_manager_spec.rb +130 -0
  106. data/spec/invoker/reactor_spec.rb +6 -0
  107. data/spec/spec_helper.rb +43 -0
  108. metadata +389 -0
@@ -0,0 +1,137 @@
1
+ module Invoker
2
+ module Power
3
+ class OsxSetup < Setup
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
10
+
11
+ def setup_invoker
12
+ if setup_resolver_file
13
+ find_open_ports
14
+ install_resolver(port_finder.dns_port)
15
+ install_firewall(port_finder.http_port, port_finder.https_port)
16
+ # Before writing the config file, drop down to a normal user
17
+ drop_to_normal_user
18
+ create_config_file
19
+ else
20
+ Invoker::Logger.puts("Invoker is not configured to serve from subdomains".color(:red))
21
+ end
22
+ self
23
+ end
24
+
25
+ def uninstall_invoker
26
+ uninstall_invoker_flag = Invoker::CLI::Question.agree("Are you sure you want to uninstall firewall rules created by setup (y/n) : ")
27
+
28
+ if uninstall_invoker_flag
29
+ remove_resolver_file
30
+ unload_firewall_rule(true)
31
+ Invoker::Power::Config.delete
32
+ Invoker::Logger.puts("Firewall rules were removed")
33
+ end
34
+ end
35
+
36
+ def build_power_config
37
+ config = super
38
+ config[:dns_port] = port_finder.dns_port
39
+ config
40
+ end
41
+
42
+ def install_resolver(dns_port)
43
+ open_resolver_for_write { |fl| fl.write(resolve_string(dns_port)) }
44
+ rescue Errno::EACCES
45
+ Invoker::Logger.puts("Running setup requires root access, please rerun it with sudo".color(:red))
46
+ raise
47
+ end
48
+
49
+ def install_firewall(http_port, https_port)
50
+ File.open(FIREWALL_PLIST_FILE, "w") { |fl|
51
+ fl.write(plist_string(http_port, https_port))
52
+ }
53
+ unload_firewall_rule
54
+ load_firewall_rule
55
+ end
56
+
57
+ def load_firewall_rule
58
+ system("launchctl load -Fw #{FIREWALL_PLIST_FILE} 2>/dev/null")
59
+ end
60
+
61
+ def unload_firewall_rule(remove = false)
62
+ system("pfctl -a com.apple/250.InvokerFirewall -F nat 2>/dev/null")
63
+ system("launchctl unload -w #{FIREWALL_PLIST_FILE} 2>/dev/null")
64
+ system("rm -rf #{FIREWALL_PLIST_FILE}") if remove
65
+ end
66
+
67
+ # Ripped from POW code
68
+ def plist_string(http_port, https_port)
69
+ plist =<<-EOD
70
+ <?xml version="1.0" encoding="UTF-8"?>
71
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
72
+ <plist version="1.0">
73
+ <dict>
74
+ <key>Label</key>
75
+ <string>com.codemancers.invoker</string>
76
+ <key>ProgramArguments</key>
77
+ <array>
78
+ <string>sh</string>
79
+ <string>-c</string>
80
+ <string>#{firewall_command(http_port, https_port)}</string>
81
+ </array>
82
+ <key>RunAtLoad</key>
83
+ <true/>
84
+ <key>UserName</key>
85
+ <string>root</string>
86
+ </dict>
87
+ </plist>
88
+ EOD
89
+ plist
90
+ end
91
+
92
+ def resolve_string(dns_port)
93
+ string =<<-EOD
94
+ nameserver 127.0.0.1
95
+ port #{dns_port}
96
+ EOD
97
+ string
98
+ end
99
+
100
+ # Ripped from Pow code
101
+ def firewall_command(http_port, https_port)
102
+ rules = [
103
+ "rdr pass on lo0 inet proto tcp from any to any port 80 -> 127.0.0.1 port #{http_port}",
104
+ "rdr pass on lo0 inet proto tcp from any to any port 443 -> 127.0.0.1 port #{https_port}"
105
+ ].join("\n")
106
+ "echo \"#{rules}\" | pfctl -a 'com.apple/250.InvokerFirewall' -f - -E"
107
+ end
108
+
109
+ def setup_resolver_file
110
+ return true unless File.exist?(resolver_file)
111
+
112
+ Invoker::Logger.puts "Invoker has detected an existing Pow installation. We recommend "\
113
+ "that you uninstall pow and rerun this setup.".color(:red)
114
+ Invoker::Logger.puts "If you have already uninstalled Pow, proceed with installation"\
115
+ " by pressing y/n."
116
+ replace_resolver_flag = Invoker::CLI::Question.agree("Replace Pow configuration (y/n) : ")
117
+
118
+ if replace_resolver_flag
119
+ Invoker::Logger.puts "Invoker has overwritten one or more files created by Pow. "\
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)
122
+ end
123
+ replace_resolver_flag
124
+ end
125
+
126
+ private
127
+
128
+ def open_resolver_for_write
129
+ FileUtils.mkdir(RESOLVER_DIR) unless Dir.exist?(RESOLVER_DIR)
130
+ fl = File.open(resolver_file, "w")
131
+ yield fl
132
+ ensure
133
+ fl && fl.close
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,40 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Invoker</title>
6
+ <style>
7
+ body {
8
+ margin: 0;
9
+ padding: 0;
10
+ background: #fff;
11
+ line-height: 18px;
12
+ }
13
+ div.page {
14
+ padding: 36px 90px;
15
+ }
16
+ h1, h2, p, li {
17
+ font-family: Helvetica, sans-serif;
18
+ font-size: 13px;
19
+ }
20
+ h1 {
21
+ line-height: 45px;
22
+ font-size: 36px;
23
+ margin: 0;
24
+ }
25
+ h2 {
26
+ line-height: 27px;
27
+ font-size: 18px;
28
+ font-weight: normal;
29
+ margin: 0;
30
+ }
31
+ </style>
32
+ </head>
33
+ <body class="">
34
+ <div class="page">
35
+ <h1>Bad request</h1>
36
+ <hr>
37
+ <h2>Invoker couldn't understand the request</h2>
38
+ </div>
39
+ </body>
40
+ </html>
@@ -0,0 +1,40 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Invoker</title>
6
+ <style>
7
+ body {
8
+ margin: 0;
9
+ padding: 0;
10
+ background: #fff;
11
+ line-height: 18px;
12
+ }
13
+ div.page {
14
+ padding: 36px 90px;
15
+ }
16
+ h1, h2, p, li {
17
+ font-family: Helvetica, sans-serif;
18
+ font-size: 13px;
19
+ }
20
+ h1 {
21
+ line-height: 45px;
22
+ font-size: 36px;
23
+ margin: 0;
24
+ }
25
+ h2 {
26
+ line-height: 27px;
27
+ font-size: 18px;
28
+ font-weight: normal;
29
+ margin: 0;
30
+ }
31
+ </style>
32
+ </head>
33
+ <body class="">
34
+ <div class="page">
35
+ <h1>Application not found</h1>
36
+ <hr>
37
+ <h2>Invoker could not find the application. Please check the configuration file.<h2>
38
+ </div>
39
+ </body>
40
+ </html>
@@ -0,0 +1,40 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>Invoker</title>
6
+ <style>
7
+ body {
8
+ margin: 0;
9
+ padding: 0;
10
+ background: #fff;
11
+ line-height: 18px;
12
+ }
13
+ div.page {
14
+ padding: 36px 90px;
15
+ }
16
+ h1, h2, p, li {
17
+ font-family: Helvetica, sans-serif;
18
+ font-size: 13px;
19
+ }
20
+ h1 {
21
+ line-height: 45px;
22
+ font-size: 36px;
23
+ margin: 0;
24
+ }
25
+ h2 {
26
+ line-height: 27px;
27
+ font-size: 18px;
28
+ font-weight: normal;
29
+ margin: 0;
30
+ }
31
+ </style>
32
+ </head>
33
+ <body class="">
34
+ <div class="page">
35
+ <h1>Application not running</h1>
36
+ <hr>
37
+ <h2>Invoker did not get any response. Please check if the application is running.<h2>
38
+ </div>
39
+ </body>
40
+ </html>
@@ -0,0 +1,40 @@
1
+ module Invoker
2
+ module Power
3
+ class UrlRewriter
4
+ def select_backend_config(complete_path)
5
+ possible_matches = extract_host_from_domain(complete_path)
6
+ exact_match = nil
7
+ possible_matches.each do |match|
8
+ if match
9
+ exact_match = dns_check(process_name: match)
10
+ break if exact_match.port
11
+ end
12
+ end
13
+ exact_match
14
+ end
15
+
16
+ def extract_host_from_domain(complete_path)
17
+ matching_strings = []
18
+ tld_match_regex.map do |regexp|
19
+ if (match_result = complete_path.match(regexp))
20
+ matching_strings << match_result[1]
21
+ end
22
+ end
23
+ matching_strings.uniq
24
+ end
25
+
26
+ private
27
+
28
+ def tld_match_regex
29
+ tld = Invoker.config.tld
30
+ [/([\w.-]+)\.#{tld}(\:\d+)?$/, /([\w-]+)\.#{tld}(\:\d+)?$/]
31
+ end
32
+
33
+ def dns_check(dns_args)
34
+ Invoker::IPC::UnixClient.send_command("dns_check", dns_args) do |dns_response|
35
+ dns_response
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,198 @@
1
+ module Invoker
2
+ # Class is responsible for managing all the processes Invoker is supposed
3
+ # to manage. Takes care of starting, stopping and restarting processes.
4
+ class ProcessManager
5
+ LABEL_COLORS = [:green, :yellow, :blue, :magenta, :cyan]
6
+ attr_accessor :open_pipes, :workers
7
+
8
+ def initialize
9
+ @open_pipes = {}
10
+ @workers = {}
11
+ @worker_mutex = Mutex.new
12
+ @thread_group = ThreadGroup.new
13
+ end
14
+
15
+ def start_process(process_info)
16
+ m, s = PTY.open
17
+ s.raw! # disable newline conversion.
18
+
19
+ pid = run_command(process_info, s)
20
+
21
+ s.close
22
+
23
+ worker = CommandWorker.new(process_info.label, m, pid, select_color)
24
+
25
+ add_worker(worker)
26
+ wait_on_pid(process_info.label, pid)
27
+ end
28
+
29
+ # Start a process given their name
30
+ # @param process_name [String] Command label of process specified in config file.
31
+ def start_process_by_name(process_name)
32
+ if process_running?(process_name)
33
+ Invoker::Logger.puts "\nProcess '#{process_name}' is already running".color(:red)
34
+ return false
35
+ end
36
+
37
+ process_info = Invoker.config.process(process_name)
38
+ start_process(process_info) if process_info
39
+ end
40
+
41
+ # Remove a process from list of processes managed by invoker supervisor.It also
42
+ # kills the process before removing it from the list.
43
+ #
44
+ # @param remove_message [Invoker::IPC::Message::Remove]
45
+ # @return [Boolean] if process existed and was removed else false
46
+ def stop_process(remove_message)
47
+ worker = workers[remove_message.process_name]
48
+ command_label = remove_message.process_name
49
+ return false unless worker
50
+ signal_to_use = remove_message.signal || 'INT'
51
+
52
+ Invoker::Logger.puts("Removing #{command_label} with signal #{signal_to_use}".color(:red))
53
+ kill_or_remove_process(worker.pid, signal_to_use, command_label)
54
+ end
55
+
56
+ # Receive a message from user to restart a Process
57
+ # @param [Invoker::IPC::Message::Reload]
58
+ def restart_process(reload_message)
59
+ command_label = reload_message.process_name
60
+ if stop_process(reload_message.remove_message)
61
+ Invoker.commander.schedule_event(command_label, :worker_removed) do
62
+ start_process_by_name(command_label)
63
+ end
64
+ else
65
+ start_process_by_name(command_label)
66
+ end
67
+ end
68
+
69
+ def run_power_server
70
+ return unless Invoker.can_run_balancer?(false)
71
+
72
+ powerup_id = Invoker::Power::Powerup.fork_and_start
73
+ wait_on_pid("powerup_manager", powerup_id)
74
+ at_exit do
75
+ begin
76
+ Process.kill("INT", powerup_id)
77
+ rescue Errno::ESRCH; end
78
+ end
79
+ end
80
+
81
+ # Given a file descriptor returns the worker object
82
+ #
83
+ # @param fd [IO] an IO object with valid file descriptor
84
+ # @return [Invoker::CommandWorker] The worker object which is associated with this fd
85
+ def get_worker_from_fd(fd)
86
+ open_pipes[fd.fileno]
87
+ end
88
+
89
+ def load_env(directory = nil)
90
+ directory ||= ENV['PWD']
91
+
92
+ if !directory || directory.empty? || !Dir.exist?(directory)
93
+ return {}
94
+ end
95
+
96
+ default_env = File.join(directory, '.env')
97
+ local_env = File.join(directory, '.env.local')
98
+ env = {}
99
+
100
+ if File.exist?(default_env)
101
+ env.merge!(Dotenv::Environment.new(default_env))
102
+ end
103
+
104
+ if File.exist?(local_env)
105
+ env.merge!(Dotenv::Environment.new(local_env))
106
+ end
107
+
108
+ env
109
+ end
110
+
111
+ def kill_workers
112
+ @workers.each do |key, worker|
113
+ kill_or_remove_process(worker.pid, "INT", worker.command_label)
114
+ end
115
+ @workers = {}
116
+ end
117
+
118
+ # List currently running commands
119
+ def process_list
120
+ Invoker::IPC::Message::ListResponse.from_workers(workers)
121
+ end
122
+
123
+ private
124
+
125
+ def wait_on_pid(command_label, pid)
126
+ raise Invoker::Errors::ToomanyOpenConnections if @thread_group.enclosed?
127
+
128
+ thread = Thread.new do
129
+ Process.wait(pid)
130
+ message = "Process with command #{command_label} exited with status #{$?.exitstatus}"
131
+ Invoker::Logger.puts("\n#{message}".color(:red))
132
+ Invoker.notify_user(message)
133
+ Invoker.commander.trigger(command_label, :exit)
134
+ end
135
+ @thread_group.add(thread)
136
+ end
137
+
138
+ def select_color
139
+ selected_color = LABEL_COLORS.shift
140
+ LABEL_COLORS.push(selected_color)
141
+ selected_color
142
+ end
143
+
144
+ def process_running?(command_label)
145
+ !!workers[command_label]
146
+ end
147
+
148
+ def kill_or_remove_process(pid, signal_to_use, command_label)
149
+ process_kill(pid, signal_to_use)
150
+ true
151
+ rescue Errno::ESRCH
152
+ Invoker::Logger.puts("Killing process with #{pid} and name #{command_label} failed".color(:red))
153
+ remove_worker(command_label, false)
154
+ false
155
+ end
156
+
157
+ def process_kill(pid, signal_to_use)
158
+ if signal_to_use.to_i == 0
159
+ Process.kill(signal_to_use, -Process.getpgid(pid))
160
+ else
161
+ Process.kill(signal_to_use.to_i, -Process.getpgid(pid))
162
+ end
163
+ end
164
+
165
+ # Remove worker from all collections
166
+ def remove_worker(command_label, trigger_event = true)
167
+ worker = @workers[command_label]
168
+ if worker
169
+ @open_pipes.delete(worker.pipe_end.fileno)
170
+ @workers.delete(command_label)
171
+ end
172
+ if trigger_event
173
+ Invoker.commander.trigger(command_label, :worker_removed)
174
+ end
175
+ end
176
+
177
+ # add worker to global collections
178
+ def add_worker(worker)
179
+ @open_pipes[worker.pipe_end.fileno] = worker
180
+ @workers[worker.command_label] = worker
181
+ Invoker.commander.watch_for_read(worker.pipe_end)
182
+ end
183
+
184
+ def run_command(process_info, write_pipe)
185
+ command_label = process_info.label
186
+
187
+ Invoker.commander.schedule_event(command_label, :exit) { remove_worker(command_label) }
188
+
189
+ env_options = load_env(process_info.dir)
190
+
191
+ spawn_options = {
192
+ :chdir => process_info.dir || ENV['PWD'], :out => write_pipe, :err => write_pipe,
193
+ :pgroup => true, :close_others => true, :in => :close
194
+ }
195
+ Invoker.run_without_bundler { spawn(env_options, process_info.cmd, spawn_options) }
196
+ end
197
+ end
198
+ end