invoker 1.0.4 → 1.1.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.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.rubocop.yml +30 -0
- data/.travis.yml +1 -0
- data/Gemfile +1 -0
- data/bin/invoker +4 -8
- data/invoker.gemspec +10 -11
- data/lib/invoker.rb +95 -21
- data/lib/invoker/cli.rb +126 -0
- data/lib/invoker/cli/pinger.rb +23 -0
- data/lib/invoker/cli/question.rb +15 -0
- data/lib/invoker/cli/tail.rb +34 -0
- data/lib/invoker/cli/tail_watcher.rb +34 -0
- data/lib/invoker/command_worker.rb +28 -2
- data/lib/invoker/commander.rb +34 -236
- data/lib/invoker/config.rb +5 -0
- data/lib/invoker/daemon.rb +126 -0
- data/lib/invoker/dns_cache.rb +23 -0
- data/lib/invoker/errors.rb +1 -0
- data/lib/invoker/ipc.rb +45 -0
- data/lib/invoker/ipc/add_command.rb +12 -0
- data/lib/invoker/ipc/add_http_command.rb +10 -0
- data/lib/invoker/ipc/base_command.rb +24 -0
- data/lib/invoker/ipc/client_handler.rb +26 -0
- data/lib/invoker/ipc/dns_check_command.rb +16 -0
- data/lib/invoker/ipc/list_command.rb +11 -0
- data/lib/invoker/ipc/message.rb +170 -0
- data/lib/invoker/ipc/message/list_response.rb +33 -0
- data/lib/invoker/ipc/message/tail_response.rb +10 -0
- data/lib/invoker/ipc/ping_command.rb +10 -0
- data/lib/invoker/ipc/reload_command.rb +12 -0
- data/lib/invoker/ipc/remove_command.rb +12 -0
- data/lib/invoker/{command_listener → ipc}/server.rb +6 -11
- data/lib/invoker/ipc/tail_command.rb +11 -0
- data/lib/invoker/ipc/unix_client.rb +60 -0
- data/lib/invoker/parsers/config.rb +1 -0
- data/lib/invoker/power/balancer.rb +17 -7
- data/lib/invoker/power/config.rb +6 -3
- data/lib/invoker/power/dns.rb +22 -21
- data/lib/invoker/power/http_response.rb +1 -1
- data/lib/invoker/power/power.rb +3 -0
- data/lib/invoker/power/powerup.rb +3 -2
- data/lib/invoker/power/setup.rb +6 -4
- data/lib/invoker/process_manager.rb +187 -0
- data/lib/invoker/process_printer.rb +27 -38
- data/lib/invoker/reactor.rb +19 -38
- data/lib/invoker/reactor/reader.rb +53 -0
- data/lib/invoker/version.rb +1 -1
- data/readme.md +1 -1
- data/spec/invoker/cli/pinger_spec.rb +22 -0
- data/spec/invoker/cli/tail_watcher_spec.rb +39 -0
- data/spec/invoker/cli_spec.rb +27 -0
- data/spec/invoker/command_worker_spec.rb +30 -0
- data/spec/invoker/commander_spec.rb +57 -127
- data/spec/invoker/config_spec.rb +21 -0
- data/spec/invoker/daemon_spec.rb +34 -0
- data/spec/invoker/invoker_spec.rb +31 -0
- data/spec/invoker/ipc/client_handler_spec.rb +44 -0
- data/spec/invoker/ipc/dns_check_command_spec.rb +32 -0
- data/spec/invoker/ipc/message/list_response_spec.rb +22 -0
- data/spec/invoker/ipc/message_spec.rb +45 -0
- data/spec/invoker/ipc/unix_client_spec.rb +29 -0
- data/spec/invoker/power/setup_spec.rb +1 -1
- data/spec/invoker/process_manager_spec.rb +98 -0
- data/spec/invoker/reactor_spec.rb +6 -0
- data/spec/spec_helper.rb +15 -24
- metadata +107 -77
- data/lib/invoker/command_listener/client.rb +0 -45
- data/lib/invoker/parsers/option_parser.rb +0 -106
- data/lib/invoker/power.rb +0 -7
- data/lib/invoker/runner.rb +0 -98
- data/spec/invoker/command_listener/client_spec.rb +0 -52
@@ -0,0 +1,11 @@
|
|
1
|
+
module Invoker
|
2
|
+
module IPC
|
3
|
+
class TailCommand < BaseCommand
|
4
|
+
def run_command(message_object)
|
5
|
+
Invoker::Logger.puts("Adding #{message_object.process_names.inspect}")
|
6
|
+
Invoker.tail_watchers.add(message_object.process_names, client_socket)
|
7
|
+
false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Invoker
|
2
|
+
module IPC
|
3
|
+
class UnixClient
|
4
|
+
def send_command(command, message = {})
|
5
|
+
message_object = get_message_object(command, message)
|
6
|
+
open_client_socket do |socket|
|
7
|
+
send_json_message(socket, message_object)
|
8
|
+
socket.flush
|
9
|
+
if block_given?
|
10
|
+
response_object = Invoker::IPC.message_from_io(socket)
|
11
|
+
yield response_object
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def send_and_receive(command, message = {})
|
17
|
+
response = nil
|
18
|
+
message_object = get_message_object(command, message)
|
19
|
+
open_client_socket(false) do |socket|
|
20
|
+
send_json_message(socket, message_object)
|
21
|
+
socket.flush
|
22
|
+
response = Invoker::IPC.message_from_io(socket)
|
23
|
+
end
|
24
|
+
response
|
25
|
+
end
|
26
|
+
|
27
|
+
def send_and_wait(command, message = {})
|
28
|
+
begin
|
29
|
+
socket = Socket.unix(Invoker::IPC::Server::SOCKET_PATH)
|
30
|
+
rescue
|
31
|
+
abort("Invoker does not seem to be running".color(:red))
|
32
|
+
end
|
33
|
+
message_object = get_message_object(command, message)
|
34
|
+
send_json_message(socket, message_object)
|
35
|
+
socket.flush
|
36
|
+
socket
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.send_command(command, message_arguments = {}, &block)
|
40
|
+
new.send_command(command, message_arguments, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def get_message_object(command, message_arguments)
|
46
|
+
Invoker::IPC::Message.const_get(Invoker::IPC.camelize(command)).new(message_arguments)
|
47
|
+
end
|
48
|
+
|
49
|
+
def open_client_socket(abort_if_not_running = true)
|
50
|
+
Socket.unix(Invoker::IPC::Server::SOCKET_PATH) { |socket| yield socket }
|
51
|
+
rescue
|
52
|
+
abort_if_not_running && abort("Invoker does not seem to be running".color(:red))
|
53
|
+
end
|
54
|
+
|
55
|
+
def send_json_message(socket, message_object)
|
56
|
+
socket.write(message_object.encoded_message)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'em-proxy'
|
2
|
+
require 'http-parser'
|
3
|
+
|
1
4
|
module Invoker
|
2
5
|
module Power
|
3
6
|
class BalancerConnection < EventMachine::ProxyServer::Connection
|
@@ -45,7 +48,7 @@ module Invoker
|
|
45
48
|
DEV_MATCH_REGEX = /([\w-]+)\.dev(\:\d+)?$/
|
46
49
|
|
47
50
|
def self.run(options = {})
|
48
|
-
EventMachine.start_server('0.0.0.0', Invoker
|
51
|
+
EventMachine.start_server('0.0.0.0', Invoker.config.http_port,
|
49
52
|
BalancerConnection, options) do |connection|
|
50
53
|
balancer = Balancer.new(connection)
|
51
54
|
balancer.install_callbacks
|
@@ -68,9 +71,9 @@ module Invoker
|
|
68
71
|
|
69
72
|
def headers_received(header)
|
70
73
|
@session = UUID.generate()
|
71
|
-
|
72
|
-
if
|
73
|
-
connection.server(session, host: '0.0.0.0', port:
|
74
|
+
dns_check_response = select_backend_config(header['Host'])
|
75
|
+
if dns_check_response && dns_check_response.port
|
76
|
+
connection.server(session, host: '0.0.0.0', port: dns_check_response.port)
|
74
77
|
connection.relay_to_servers(@buffer.join)
|
75
78
|
@buffer = []
|
76
79
|
else
|
@@ -102,13 +105,13 @@ module Invoker
|
|
102
105
|
end
|
103
106
|
|
104
107
|
def frontend_disconnect(backend, name)
|
105
|
-
http_parser.reset
|
108
|
+
http_parser.reset
|
106
109
|
unless @backend_data
|
107
110
|
Invoker::Logger.puts("\nApplication not running. Returning error page.".color(:red))
|
108
111
|
return_error_page(503)
|
109
112
|
end
|
110
113
|
@backend_data = false
|
111
|
-
connection.close_connection_after_writing
|
114
|
+
connection.close_connection_after_writing if backend == session
|
112
115
|
end
|
113
116
|
|
114
117
|
def extract_host_from_domain(host)
|
@@ -116,16 +119,23 @@ module Invoker
|
|
116
119
|
end
|
117
120
|
|
118
121
|
private
|
122
|
+
|
119
123
|
def select_backend_config(host)
|
120
124
|
matching_string = extract_host_from_domain(host)
|
121
125
|
return nil unless matching_string
|
122
126
|
if selected_app = matching_string[1]
|
123
|
-
|
127
|
+
dns_check(process_name: selected_app)
|
124
128
|
else
|
125
129
|
nil
|
126
130
|
end
|
127
131
|
end
|
128
132
|
|
133
|
+
def dns_check(dns_args)
|
134
|
+
Invoker::IPC::UnixClient.send_command("dns_check", dns_args) do |dns_response|
|
135
|
+
dns_response
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
129
139
|
def return_error_page(status)
|
130
140
|
http_response = Invoker::Power::HttpResponse.new()
|
131
141
|
http_response.status = status
|
data/lib/invoker/power/config.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
require "yaml"
|
2
|
+
|
2
3
|
module Invoker
|
3
4
|
module Power
|
4
5
|
# Save and Load Invoker::Power config
|
5
6
|
class ConfigExists < StandardError; end
|
7
|
+
|
6
8
|
class Config
|
7
|
-
CONFIG_LOCATION = File.join(
|
9
|
+
CONFIG_LOCATION = File.join(Dir.home, ".invoker", "config")
|
10
|
+
|
8
11
|
def self.has_config?
|
9
|
-
File.
|
12
|
+
File.exist?(CONFIG_LOCATION)
|
10
13
|
end
|
11
14
|
|
12
15
|
def self.create(options = {})
|
@@ -18,7 +21,7 @@ module Invoker
|
|
18
21
|
end
|
19
22
|
|
20
23
|
def self.delete
|
21
|
-
if File.
|
24
|
+
if File.exist?(CONFIG_LOCATION)
|
22
25
|
File.delete(CONFIG_LOCATION)
|
23
26
|
end
|
24
27
|
end
|
data/lib/invoker/power/dns.rb
CHANGED
@@ -1,37 +1,38 @@
|
|
1
1
|
require "logger"
|
2
|
+
require 'rubydns'
|
2
3
|
|
3
4
|
module Invoker
|
4
|
-
|
5
5
|
module Power
|
6
|
-
|
7
|
-
class DNS
|
8
|
-
IN = Resolv::DNS::Resource::IN
|
6
|
+
class DNS < RubyDNS::Server
|
9
7
|
def self.server_ports
|
10
8
|
[
|
11
|
-
[:udp, '127.0.0.1', Invoker
|
12
|
-
[:tcp, '127.0.0.1', Invoker
|
9
|
+
[:udp, '127.0.0.1', Invoker.config.dns_port],
|
10
|
+
[:tcp, '127.0.0.1', Invoker.config.dns_port]
|
13
11
|
]
|
14
12
|
end
|
15
13
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
# For this exact address record, return an IP address
|
23
|
-
match(/.*\.dev/, IN::A) do |transaction|
|
24
|
-
transaction.respond!("127.0.0.1")
|
25
|
-
end
|
14
|
+
def initialize
|
15
|
+
@logger = ::Logger.new($stderr)
|
16
|
+
@logger.level = ::Logger::FATAL
|
17
|
+
end
|
26
18
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
19
|
+
def process(name, resource_class, transaction)
|
20
|
+
if name_matches?(name) && resource_class_matches?(resource_class)
|
21
|
+
transaction.respond!("127.0.0.1")
|
22
|
+
else
|
23
|
+
transaction.fail!(:NXDomain)
|
31
24
|
end
|
32
25
|
end
|
33
26
|
|
34
|
-
|
27
|
+
private
|
35
28
|
|
29
|
+
def resource_class_matches?(resource_class)
|
30
|
+
resource_class == Resolv::DNS::Resource::IN::A
|
31
|
+
end
|
32
|
+
|
33
|
+
def name_matches?(name)
|
34
|
+
name =~ /.*\.dev/
|
35
|
+
end
|
36
|
+
end
|
36
37
|
end
|
37
38
|
end
|
@@ -8,12 +8,13 @@ module Invoker
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def run
|
11
|
+
require "invoker/power/power"
|
11
12
|
EM.epoll
|
12
13
|
EM.run {
|
13
14
|
trap("TERM") { stop }
|
14
15
|
trap("INT") { stop }
|
15
|
-
DNS.
|
16
|
-
Balancer.run
|
16
|
+
DNS.new.run(listen: DNS.server_ports)
|
17
|
+
Balancer.run
|
17
18
|
}
|
18
19
|
end
|
19
20
|
|
data/lib/invoker/power/setup.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require "
|
1
|
+
require "eventmachine"
|
2
2
|
|
3
3
|
module Invoker
|
4
4
|
module Power
|
@@ -42,7 +42,7 @@ module Invoker
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def uninstall_invoker
|
45
|
-
uninstall_invoker_flag = agree("Are you sure you want to uninstall firewall rules created by setup (y/n) : ")
|
45
|
+
uninstall_invoker_flag = Invoker::CLI::Question.agree("Are you sure you want to uninstall firewall rules created by setup (y/n) : ")
|
46
46
|
|
47
47
|
if uninstall_invoker_flag
|
48
48
|
remove_resolver_file
|
@@ -62,6 +62,7 @@ module Invoker
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def create_config_file
|
65
|
+
Invoker.setup_config_location
|
65
66
|
Invoker::Power::Config.create(
|
66
67
|
dns_port: port_finder.dns_port,
|
67
68
|
http_port: port_finder.http_port
|
@@ -162,7 +163,7 @@ port #{dns_port}
|
|
162
163
|
Invoker::Logger.puts "If you have already uninstalled Pow, proceed with installation"\
|
163
164
|
" by pressing y/n."
|
164
165
|
|
165
|
-
replace_resolver_flag = agree("Replace Pow configuration (y/n) : ")
|
166
|
+
replace_resolver_flag = Invoker::CLI::Question.agree("Replace Pow configuration (y/n) : ")
|
166
167
|
|
167
168
|
if replace_resolver_flag
|
168
169
|
Invoker::Logger.puts "Invoker has overwritten one or more files created by Pow. "\
|
@@ -173,12 +174,13 @@ port #{dns_port}
|
|
173
174
|
end
|
174
175
|
|
175
176
|
private
|
177
|
+
|
176
178
|
def open_resolver_for_write
|
177
179
|
FileUtils.mkdir(RESOLVER_DIR) unless Dir.exists?(RESOLVER_DIR)
|
178
180
|
fl = File.open(RESOLVER_FILE, "w")
|
179
181
|
yield fl
|
180
182
|
ensure
|
181
|
-
fl && fl.close
|
183
|
+
fl && fl.close
|
182
184
|
end
|
183
185
|
|
184
186
|
end
|
@@ -0,0 +1,187 @@
|
|
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
|
+
default_env = File.join(directory, '.env')
|
92
|
+
return {} unless File.exist?(default_env)
|
93
|
+
Dotenv::Environment.new(default_env)
|
94
|
+
end
|
95
|
+
|
96
|
+
def kill_workers
|
97
|
+
@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
|
103
|
+
end
|
104
|
+
@workers = {}
|
105
|
+
end
|
106
|
+
|
107
|
+
# List currently running commands
|
108
|
+
def process_list
|
109
|
+
Invoker::IPC::Message::ListResponse.from_workers(workers)
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def wait_on_pid(command_label, pid)
|
115
|
+
raise Invoker::Errors::ToomanyOpenConnections if @thread_group.enclosed?
|
116
|
+
|
117
|
+
thread = Thread.new do
|
118
|
+
Process.wait(pid)
|
119
|
+
message = "Process with command #{command_label} exited with status #{$?.exitstatus}"
|
120
|
+
Invoker::Logger.puts("\n#{message}".color(:red))
|
121
|
+
Invoker.notify_user(message)
|
122
|
+
Invoker.commander.trigger(command_label, :exit)
|
123
|
+
end
|
124
|
+
@thread_group.add(thread)
|
125
|
+
end
|
126
|
+
|
127
|
+
def select_color
|
128
|
+
selected_color = LABEL_COLORS.shift
|
129
|
+
LABEL_COLORS.push(selected_color)
|
130
|
+
selected_color
|
131
|
+
end
|
132
|
+
|
133
|
+
def process_running?(command_label)
|
134
|
+
!!workers[command_label]
|
135
|
+
end
|
136
|
+
|
137
|
+
def kill_or_remove_process(pid, signal_to_use, command_label)
|
138
|
+
process_kill(pid, signal_to_use)
|
139
|
+
true
|
140
|
+
rescue Errno::ESRCH
|
141
|
+
Invoker::Logger.puts("Killing process with #{pid} and name #{command_label} failed".color(:red))
|
142
|
+
remove_worker(command_label, false)
|
143
|
+
false
|
144
|
+
end
|
145
|
+
|
146
|
+
def process_kill(pid, signal_to_use)
|
147
|
+
if signal_to_use.to_i == 0
|
148
|
+
Process.kill(signal_to_use, -Process.getpgid(pid))
|
149
|
+
else
|
150
|
+
Process.kill(signal_to_use.to_i, -Process.getpgid(pid))
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Remove worker from all collections
|
155
|
+
def remove_worker(command_label, trigger_event = true)
|
156
|
+
worker = @workers[command_label]
|
157
|
+
if worker
|
158
|
+
@open_pipes.delete(worker.pipe_end.fileno)
|
159
|
+
@workers.delete(command_label)
|
160
|
+
end
|
161
|
+
if trigger_event
|
162
|
+
Invoker.commander.trigger(command_label, :worker_removed)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# add worker to global collections
|
167
|
+
def add_worker(worker)
|
168
|
+
@open_pipes[worker.pipe_end.fileno] = worker
|
169
|
+
@workers[worker.command_label] = worker
|
170
|
+
Invoker.commander.watch_for_read(worker.pipe_end)
|
171
|
+
end
|
172
|
+
|
173
|
+
def run_command(process_info, write_pipe)
|
174
|
+
command_label = process_info.label
|
175
|
+
|
176
|
+
Invoker.commander.schedule_event(command_label, :exit) { remove_worker(command_label) }
|
177
|
+
|
178
|
+
env_options = load_env(process_info.dir)
|
179
|
+
|
180
|
+
spawn_options = {
|
181
|
+
:chdir => process_info.dir || ENV['PWD'], :out => write_pipe, :err => write_pipe,
|
182
|
+
:pgroup => true, :close_others => true, :in => :close
|
183
|
+
}
|
184
|
+
Invoker.run_without_bundler { spawn(env_options, process_info.cmd, spawn_options) }
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|