foreman 0.46.0-mingw32 → 0.50.0-mingw32
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.
- data/README.md +6 -0
- data/bin/foreman-runner +3 -7
- data/bin/taskman +8 -0
- data/data/example/Procfile +4 -3
- data/data/example/spawnee +14 -0
- data/data/example/spawner +7 -0
- data/data/export/bluepill/master.pill.erb +11 -10
- data/data/export/launchd/launchd.plist.erb +22 -0
- data/data/export/runit/log/run.erb +7 -0
- data/data/export/runit/run.erb +2 -2
- data/data/export/supervisord/app.conf.erb +12 -12
- data/data/export/upstart/master.conf.erb +2 -2
- data/data/export/upstart/process.conf.erb +3 -3
- data/lib/foreman.rb +4 -0
- data/lib/foreman/cli.rb +59 -23
- data/lib/foreman/engine.rb +233 -147
- data/lib/foreman/engine/cli.rb +105 -0
- data/lib/foreman/env.rb +27 -0
- data/lib/foreman/export.rb +2 -2
- data/lib/foreman/export/base.rb +107 -12
- data/lib/foreman/export/bluepill.rb +3 -17
- data/lib/foreman/export/inittab.rb +8 -11
- data/lib/foreman/export/launchd.rb +15 -0
- data/lib/foreman/export/runit.rb +14 -39
- data/lib/foreman/export/supervisord.rb +3 -13
- data/lib/foreman/export/upstart.rb +9 -27
- data/lib/foreman/process.rb +73 -67
- data/lib/foreman/procfile.rb +59 -25
- data/lib/foreman/version.rb +1 -1
- data/man/foreman.1 +5 -1
- data/spec/foreman/cli_spec.rb +46 -150
- data/spec/foreman/engine_spec.rb +47 -74
- data/spec/foreman/export/base_spec.rb +4 -7
- data/spec/foreman/export/bluepill_spec.rb +7 -6
- data/spec/foreman/export/inittab_spec.rb +7 -7
- data/spec/foreman/export/launchd_spec.rb +21 -0
- data/spec/foreman/export/runit_spec.rb +12 -17
- data/spec/foreman/export/supervisord_spec.rb +7 -56
- data/spec/foreman/export/upstart_spec.rb +22 -21
- data/spec/foreman/process_spec.rb +27 -110
- data/spec/foreman/procfile_spec.rb +26 -16
- data/spec/resources/Procfile +4 -0
- data/spec/resources/bin/echo +2 -0
- data/spec/resources/bin/env +2 -0
- data/spec/resources/bin/test +2 -0
- data/spec/resources/export/bluepill/app-concurrency.pill +6 -4
- data/spec/resources/export/bluepill/app.pill +6 -4
- data/spec/resources/export/launchd/launchd-a.default +22 -0
- data/spec/resources/export/launchd/launchd-b.default +22 -0
- data/spec/resources/export/runit/{app-alpha-1-log-run → app-alpha-1/log/run} +0 -0
- data/spec/resources/export/runit/{app-alpha-1-run → app-alpha-1/run} +0 -0
- data/spec/resources/export/runit/{app-alpha-2-log-run → app-alpha-2/log/run} +0 -0
- data/spec/resources/export/runit/{app-alpha-2-run → app-alpha-2/run} +0 -0
- data/spec/resources/export/runit/{app-bravo-1-log-run → app-bravo-1/log/run} +0 -0
- data/spec/resources/export/runit/{app-bravo-1-run → app-bravo-1/run} +0 -0
- data/spec/resources/export/supervisord/app-alpha-1.conf +24 -0
- data/spec/resources/export/supervisord/app-alpha-2.conf +4 -4
- data/spec/spec_helper.rb +57 -6
- metadata +29 -21
- data/data/export/runit/log_run.erb +0 -7
- data/lib/foreman/color.rb +0 -40
- data/lib/foreman/procfile_entry.rb +0 -26
- data/lib/foreman/utils.rb +0 -18
- data/spec/foreman/color_spec.rb +0 -31
- data/spec/foreman/procfile_entry_spec.rb +0 -13
- data/spec/resources/export/supervisord/app-env-with-comma.conf +0 -24
- data/spec/resources/export/supervisord/app-env.conf +0 -21
- data/spec/resources/export/supervisord/app.conf +0 -24
data/README.md
CHANGED
@@ -27,6 +27,12 @@ Manage Procfile-based applications
|
|
27
27
|
* [wiki](http://github.com/ddollar/foreman/wiki)
|
28
28
|
* [changelog](https://github.com/ddollar/foreman/blob/master/Changelog.md)
|
29
29
|
|
30
|
+
## Ports
|
31
|
+
|
32
|
+
* [shoreman](https://github.com/hecticjeff/shoreman) - shell
|
33
|
+
* [honcho](https://github.com/nickstenning/honcho) - python
|
34
|
+
* [norman](https://github.com/josh/norman) - node.js
|
35
|
+
|
30
36
|
## Authors
|
31
37
|
|
32
38
|
#### Created and maintained by
|
data/bin/foreman-runner
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/bin/sh
|
2
2
|
#
|
3
|
-
#/ Usage: foreman-runner [-d <dir>] <command>
|
3
|
+
#/ Usage: foreman-runner [-d <dir>] <command> [<args>...]
|
4
4
|
#/
|
5
5
|
#/ Run a command with exec, optionally changing directory first
|
6
6
|
|
@@ -27,10 +27,6 @@ done
|
|
27
27
|
|
28
28
|
shift $((OPTIND-1))
|
29
29
|
|
30
|
-
|
30
|
+
[ -z "$1" ] && usage
|
31
31
|
|
32
|
-
|
33
|
-
usage
|
34
|
-
fi
|
35
|
-
|
36
|
-
exec $1
|
32
|
+
exec "$@"
|
data/bin/taskman
ADDED
data/data/example/Procfile
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
-
ticker:
|
2
|
-
error:
|
3
|
-
utf8:
|
1
|
+
ticker: ruby ./ticker $PORT
|
2
|
+
error: ruby ./error
|
3
|
+
utf8: ruby ./utf8
|
4
|
+
spawner: ./spawner
|
@@ -3,24 +3,25 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
|
|
3
3
|
app.uid = "<%= user %>"
|
4
4
|
app.gid = "<%= user %>"
|
5
5
|
|
6
|
-
<% engine.
|
7
|
-
<% 1.upto(
|
8
|
-
<% port = engine.port_for(process, num
|
9
|
-
app.process("<%=
|
10
|
-
process.start_command = "<%= process.command
|
6
|
+
<% engine.each_process do |name, process| %>
|
7
|
+
<% 1.upto(engine.formation[name]) do |num| %>
|
8
|
+
<% port = engine.port_for(process, num) %>
|
9
|
+
app.process("<%= name %>-<%= num %>") do |process|
|
10
|
+
process.start_command = "<%= process.command %>"
|
11
11
|
|
12
|
-
process.working_dir = "<%= engine.
|
12
|
+
process.working_dir = "<%= engine.root %>"
|
13
13
|
process.daemonize = true
|
14
|
-
process.environment =
|
14
|
+
process.environment = <%= engine.env.merge("PORT" => port.to_s).inspect %>
|
15
15
|
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
|
16
|
+
process.stop_grace_time = 45.seconds
|
16
17
|
|
17
|
-
process.stdout = process.stderr = "<%=
|
18
|
+
process.stdout = process.stderr = "<%= log %>/<%= app %>-<%= name %>-<%= num %>.log"
|
18
19
|
|
19
20
|
process.monitor_children do |children|
|
20
|
-
children.stop_command "kill
|
21
|
+
children.stop_command "kill {{PID}}"
|
21
22
|
end
|
22
23
|
|
23
|
-
process.group = "<%= app %>-<%=
|
24
|
+
process.group = "<%= app %>-<%= name %>"
|
24
25
|
end
|
25
26
|
<% end %>
|
26
27
|
<% end %>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
3
|
+
<plist version="1.0">
|
4
|
+
<dict>
|
5
|
+
<key>Label</key>
|
6
|
+
<string><%= "#{app}-#{name}-#{num}" %></string>
|
7
|
+
<key>ProgramArguments</key>
|
8
|
+
<array>
|
9
|
+
<string><%= process.command %></string>
|
10
|
+
</array>
|
11
|
+
<key>KeepAlive</key>
|
12
|
+
<true/>
|
13
|
+
<key>RunAtLoad</key>
|
14
|
+
<true/>
|
15
|
+
<key>StandardErrorPath</key>
|
16
|
+
<string><%= log %>/<%= app %>-<%= name %>-<%=num%>.log</string>
|
17
|
+
<key>UserName</key>
|
18
|
+
<string><%= user %></string>
|
19
|
+
<key>WorkingDirectory</key>
|
20
|
+
<string><%= engine.root %></string>
|
21
|
+
</dict>
|
22
|
+
</plist>
|
data/data/export/runit/run.erb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
#!/bin/sh
|
2
|
-
cd <%= engine.
|
3
|
-
exec chpst -u <%= user %> -e <%=
|
2
|
+
cd <%= engine.root %>
|
3
|
+
exec chpst -u <%= user %> -e <%= File.join(location, "#{process_directory}/env") %> <%= process.command %>
|
@@ -1,23 +1,23 @@
|
|
1
1
|
<%
|
2
2
|
app_names = []
|
3
|
-
engine.
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
app_names <<
|
3
|
+
engine.each_process do |name, process|
|
4
|
+
1.upto(engine.formation[name]) do |num|
|
5
|
+
port = engine.port_for(process, num)
|
6
|
+
full_name = "#{app}-#{name}-#{num}"
|
7
|
+
environment = engine.env.merge("PORT" => port.to_s).map do |key, value|
|
8
|
+
"#{key}=#{shell_quote(value)}"
|
9
|
+
end
|
10
|
+
app_names << full_name
|
11
11
|
%>
|
12
|
-
[program:<%=
|
12
|
+
[program:<%= full_name %>]
|
13
13
|
command=<%= process.command %>
|
14
14
|
autostart=true
|
15
15
|
autorestart=true
|
16
16
|
stopsignal=QUIT
|
17
|
-
stdout_logfile=<%=
|
18
|
-
stderr_logfile=<%=
|
17
|
+
stdout_logfile=<%= log %>/<%= name %>-<%= num %>.log
|
18
|
+
stderr_logfile=<%= log %>/<%= name %>-<%= num %>.error.log
|
19
19
|
user=<%= user %>
|
20
|
-
directory=<%= engine.
|
20
|
+
directory=<%= engine.root %>
|
21
21
|
environment=<%= environment.join(',') %><%
|
22
22
|
end
|
23
23
|
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
start on starting <%= app %>-<%=
|
2
|
-
stop on stopping <%= app %>-<%=
|
1
|
+
start on starting <%= app %>-<%= name %>
|
2
|
+
stop on stopping <%= app %>-<%= name %>
|
3
3
|
respawn
|
4
4
|
|
5
|
-
exec su - <%= user %> -c 'cd <%= engine.
|
5
|
+
exec su - <%= user %> -c 'cd <%= engine.root %>; export PORT=<%= port %>;<% engine.env.each_pair do |var,env| %> export <%= var.upcase %>=<%= shell_quote(env) %>; <% end %> <%= process.command %> >> <%= log %>/<%=name%>-<%=num%>.log 2>&1'
|
data/lib/foreman.rb
CHANGED
@@ -12,6 +12,10 @@ module Foreman
|
|
12
12
|
defined?(RUBY_PLATFORM) and RUBY_PLATFORM == "java"
|
13
13
|
end
|
14
14
|
|
15
|
+
def self.ruby_18?
|
16
|
+
defined?(RUBY_VERSION) and RUBY_VERSION =~ /^1\.8\.\d+/
|
17
|
+
end
|
18
|
+
|
15
19
|
def self.windows?
|
16
20
|
defined?(RUBY_PLATFORM) and RUBY_PLATFORM =~ /(win|w)32$/
|
17
21
|
end
|
data/lib/foreman/cli.rb
CHANGED
@@ -1,35 +1,42 @@
|
|
1
1
|
require "foreman"
|
2
2
|
require "foreman/helpers"
|
3
3
|
require "foreman/engine"
|
4
|
+
require "foreman/engine/cli"
|
4
5
|
require "foreman/export"
|
5
|
-
require "
|
6
|
+
require "foreman/version"
|
7
|
+
require "shellwords"
|
6
8
|
require "yaml"
|
9
|
+
require "thor"
|
7
10
|
|
8
11
|
class Foreman::CLI < Thor
|
12
|
+
|
9
13
|
include Foreman::Helpers
|
10
14
|
|
15
|
+
map ["-v", "--version"] => :version
|
16
|
+
|
11
17
|
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
|
18
|
+
class_option :root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
|
12
19
|
|
13
20
|
desc "start [PROCESS]", "Start the application (or a specific PROCESS)"
|
14
21
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
method_option :
|
19
|
-
method_option :port, :type => :numeric, :aliases => "-p"
|
20
|
-
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
|
22
|
+
method_option :color, :type => :boolean, :aliases => "-c", :desc => "Force color to be enabled"
|
23
|
+
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
|
24
|
+
method_option :formation, :type => :string, :aliases => "-m", :banner => '"alpha=5,bar=3"'
|
25
|
+
method_option :port, :type => :numeric, :aliases => "-p"
|
21
26
|
|
22
27
|
class << self
|
23
28
|
# Hackery. Take the run method away from Thor so that we can redefine it.
|
24
29
|
def is_thor_reserved_word?(word, type)
|
25
|
-
return false if word ==
|
30
|
+
return false if word == "run"
|
26
31
|
super
|
27
32
|
end
|
28
33
|
end
|
29
34
|
|
30
35
|
def start(process=nil)
|
31
36
|
check_procfile!
|
32
|
-
|
37
|
+
load_environment!
|
38
|
+
engine.load_procfile(procfile)
|
39
|
+
engine.options[:formation] = "#{process}=1" if process
|
33
40
|
engine.start
|
34
41
|
end
|
35
42
|
|
@@ -45,6 +52,8 @@ class Foreman::CLI < Thor
|
|
45
52
|
|
46
53
|
def export(format, location=nil)
|
47
54
|
check_procfile!
|
55
|
+
load_environment!
|
56
|
+
engine.load_procfile(procfile)
|
48
57
|
formatter = Foreman::Export.formatter(format)
|
49
58
|
formatter.new(location, engine, options).export
|
50
59
|
rescue Foreman::Export::Exception => ex
|
@@ -55,16 +64,19 @@ class Foreman::CLI < Thor
|
|
55
64
|
|
56
65
|
def check
|
57
66
|
check_procfile!
|
58
|
-
|
59
|
-
|
67
|
+
engine.load_procfile(procfile)
|
68
|
+
error "no processes defined" unless engine.processes.length > 0
|
69
|
+
puts "valid procfile detected (#{engine.process_names.join(', ')})"
|
60
70
|
end
|
61
71
|
|
62
|
-
desc "run COMMAND", "Run a command using your application's environment"
|
72
|
+
desc "run COMMAND [ARGS...]", "Run a command using your application's environment"
|
73
|
+
|
74
|
+
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
|
63
75
|
|
64
76
|
def run(*args)
|
65
|
-
|
77
|
+
load_environment!
|
66
78
|
begin
|
67
|
-
exec args.
|
79
|
+
exec engine.env, args.shelljoin
|
68
80
|
rescue Errno::EACCES
|
69
81
|
error "not executable: #{args.first}"
|
70
82
|
rescue Errno::ENOENT
|
@@ -72,33 +84,57 @@ class Foreman::CLI < Thor
|
|
72
84
|
end
|
73
85
|
end
|
74
86
|
|
87
|
+
desc "version", "Display Foreman gem version"
|
88
|
+
|
89
|
+
def version
|
90
|
+
puts Foreman::VERSION
|
91
|
+
end
|
92
|
+
|
93
|
+
no_tasks do
|
94
|
+
def engine
|
95
|
+
@engine ||= begin
|
96
|
+
engine_class = Foreman::Engine::CLI
|
97
|
+
engine = engine_class.new(options)
|
98
|
+
engine
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
75
103
|
private ######################################################################
|
76
104
|
|
105
|
+
def error(message)
|
106
|
+
puts "ERROR: #{message}"
|
107
|
+
exit 1
|
108
|
+
end
|
109
|
+
|
77
110
|
def check_procfile!
|
78
111
|
error("#{procfile} does not exist.") unless File.exist?(procfile)
|
79
112
|
end
|
80
113
|
|
81
|
-
def
|
82
|
-
|
114
|
+
def load_environment!
|
115
|
+
if options[:env]
|
116
|
+
options[:env].split(",").each do |file|
|
117
|
+
engine.load_env file
|
118
|
+
end
|
119
|
+
else
|
120
|
+
default_env = File.join(engine.root, ".env")
|
121
|
+
engine.load_env default_env if File.exists?(default_env)
|
122
|
+
end
|
83
123
|
end
|
84
124
|
|
85
125
|
def procfile
|
86
126
|
case
|
87
127
|
when options[:procfile] then options[:procfile]
|
88
|
-
when options[:
|
128
|
+
when options[:root] then File.expand_path(File.join(options[:app_root], "Procfile"))
|
89
129
|
else "Procfile"
|
90
130
|
end
|
91
131
|
end
|
92
132
|
|
93
|
-
def error(message)
|
94
|
-
puts "ERROR: #{message}"
|
95
|
-
exit 1
|
96
|
-
end
|
97
|
-
|
98
133
|
def options
|
99
134
|
original_options = super
|
100
135
|
return original_options unless File.exists?(".foreman")
|
101
|
-
defaults = YAML::load_file(".foreman") || {}
|
136
|
+
defaults = ::YAML::load_file(".foreman") || {}
|
102
137
|
Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
|
103
138
|
end
|
139
|
+
|
104
140
|
end
|
data/lib/foreman/engine.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
require "foreman"
|
2
|
-
require "foreman/
|
2
|
+
require "foreman/env"
|
3
3
|
require "foreman/process"
|
4
4
|
require "foreman/procfile"
|
5
|
-
require "foreman/utils"
|
6
5
|
require "tempfile"
|
7
6
|
require "timeout"
|
8
7
|
require "fileutils"
|
@@ -10,218 +9,305 @@ require "thread"
|
|
10
9
|
|
11
10
|
class Foreman::Engine
|
12
11
|
|
13
|
-
attr_reader :
|
14
|
-
attr_reader :procfile
|
15
|
-
attr_reader :directory
|
12
|
+
attr_reader :env
|
16
13
|
attr_reader :options
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
14
|
+
attr_reader :processes
|
15
|
+
|
16
|
+
# Create an +Engine+ for running processes
|
17
|
+
#
|
18
|
+
# @param [Hash] options
|
19
|
+
#
|
20
|
+
# @option options [String] :formation (all=1) The process formation to use
|
21
|
+
# @option options [Fixnum] :port (5000) The base port to assign to processes
|
22
|
+
# @option options [String] :root (Dir.pwd) The root directory from which to run processes
|
23
|
+
#
|
24
|
+
def initialize(options={})
|
26
25
|
@options = options.dup
|
27
|
-
@output_mutex = Mutex.new
|
28
26
|
|
29
|
-
@options[:
|
30
|
-
|
27
|
+
@options[:formation] ||= (options[:concurrency] || "all=1")
|
28
|
+
|
29
|
+
@env = {}
|
30
|
+
@mutex = Mutex.new
|
31
|
+
@names = {}
|
32
|
+
@processes = []
|
33
|
+
@running = {}
|
34
|
+
@readers = {}
|
31
35
|
end
|
32
36
|
|
37
|
+
# Start the processes registered to this +Engine+
|
38
|
+
#
|
33
39
|
def start
|
34
|
-
proctitle "ruby: foreman master"
|
35
|
-
termtitle "#{File.basename(@directory)} - foreman"
|
36
|
-
|
37
40
|
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
38
41
|
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
42
|
+
trap("HUP") { puts "SIGHUP received"; terminate_gracefully } if ::Signal.list.keys.include? 'HUP'
|
39
43
|
|
40
|
-
|
44
|
+
startup
|
41
45
|
spawn_processes
|
42
46
|
watch_for_output
|
43
|
-
|
47
|
+
sleep 0.1
|
48
|
+
watch_for_termination { terminate_gracefully }
|
49
|
+
shutdown
|
44
50
|
end
|
45
51
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
52
|
+
# Register a process to be run by this +Engine+
|
53
|
+
#
|
54
|
+
# @param [String] name A name for this process
|
55
|
+
# @param [String] command The command to run
|
56
|
+
# @param [Hash] options
|
57
|
+
#
|
58
|
+
# @option options [Hash] :env A custom environment for this process
|
59
|
+
#
|
60
|
+
def register(name, command, options={})
|
61
|
+
options[:env] ||= env
|
62
|
+
options[:cwd] ||= File.dirname(command.split(" ").first)
|
63
|
+
process = Foreman::Process.new(command, options)
|
64
|
+
@names[process] = name
|
65
|
+
@processes << process
|
50
66
|
end
|
51
67
|
|
52
|
-
|
53
|
-
|
68
|
+
# Clear the processes registered to this +Engine+
|
69
|
+
#
|
70
|
+
def clear
|
71
|
+
@names = {}
|
72
|
+
@processes = []
|
54
73
|
end
|
55
74
|
|
56
|
-
|
57
|
-
|
75
|
+
# Register processes by reading a Procfile
|
76
|
+
#
|
77
|
+
# @param [String] filename A Procfile from which to read processes to register
|
78
|
+
#
|
79
|
+
def load_procfile(filename)
|
80
|
+
options[:root] ||= File.dirname(filename)
|
81
|
+
Foreman::Procfile.new(filename).entries do |name, command|
|
82
|
+
register name, command, :cwd => options[:root]
|
83
|
+
end
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
87
|
+
# Load a .env file into the +env+ for this +Engine+
|
88
|
+
#
|
89
|
+
# @param [String] filename A .env file to load into the environment
|
90
|
+
#
|
91
|
+
def load_env(filename)
|
92
|
+
Foreman::Env.new(filename).entries do |name, value|
|
93
|
+
@env[name] = value
|
94
|
+
end
|
95
|
+
end
|
58
96
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
97
|
+
# Send a signal to all processesstarted by this +Engine+
|
98
|
+
#
|
99
|
+
# @param [String] signal The signal to send to each process
|
100
|
+
#
|
101
|
+
def killall(signal="SIGTERM")
|
102
|
+
if Foreman.windows?
|
103
|
+
@running.each do |pid, (process, index)|
|
104
|
+
system "sending #{signal} to #{name_for(pid)} at pid #{pid}"
|
105
|
+
begin
|
106
|
+
Process.kill(signal, pid)
|
107
|
+
rescue Errno::ESRCH, Errno::EPERM
|
66
108
|
end
|
67
109
|
end
|
68
|
-
|
110
|
+
else
|
111
|
+
begin
|
112
|
+
Process.kill "-#{signal}", Process.pid
|
113
|
+
rescue Errno::ESRCH, Errno::EPERM
|
114
|
+
end
|
69
115
|
end
|
70
116
|
end
|
71
117
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
118
|
+
# Get the process formation
|
119
|
+
#
|
120
|
+
# @returns [Fixnum] The formation count for the specified process
|
121
|
+
#
|
122
|
+
def formation
|
123
|
+
@formation ||= parse_formation(options[:formation])
|
124
|
+
end
|
76
125
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
end
|
126
|
+
# List the available process names
|
127
|
+
#
|
128
|
+
# @returns [Array] A list of process names
|
129
|
+
#
|
130
|
+
def process_names
|
131
|
+
@processes.map { |p| @names[p] }
|
84
132
|
end
|
85
133
|
|
86
|
-
|
87
|
-
|
134
|
+
# Get the +Process+ for a specifid name
|
135
|
+
#
|
136
|
+
# @param [String] name The process name
|
137
|
+
#
|
138
|
+
# @returns [Foreman::Process] The +Process+ for the specified name
|
139
|
+
#
|
140
|
+
def process(name)
|
141
|
+
@names.invert[name]
|
88
142
|
end
|
89
143
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
144
|
+
# Yield each +Process+ in order
|
145
|
+
#
|
146
|
+
def each_process
|
147
|
+
process_names.each do |name|
|
148
|
+
yield name, process(name)
|
94
149
|
end
|
95
150
|
end
|
96
151
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
while running_processes.length > 0
|
104
|
-
pid, status = Process.wait2
|
105
|
-
process = running_processes.delete(pid)
|
106
|
-
info "process terminated", process.name
|
107
|
-
end
|
108
|
-
end
|
109
|
-
rescue Timeout::Error
|
110
|
-
info "sending SIGKILL to all processes"
|
111
|
-
kill_all "SIGKILL"
|
112
|
-
end
|
113
|
-
|
114
|
-
def poll_readers
|
115
|
-
rs, ws = IO.select(readers.values, [], [], 1)
|
116
|
-
(rs || []).each do |r|
|
117
|
-
data = r.gets
|
118
|
-
next unless data
|
119
|
-
data.force_encoding("BINARY") if data.respond_to?(:force_encoding)
|
120
|
-
ps, message = data.split(",", 2)
|
121
|
-
color = colors[ps.split(".").first]
|
122
|
-
info message, ps, color
|
123
|
-
end
|
152
|
+
# Get the root directory for this +Engine+
|
153
|
+
#
|
154
|
+
# @returns [String] The root directory
|
155
|
+
#
|
156
|
+
def root
|
157
|
+
File.expand_path(options[:root] || Dir.pwd)
|
124
158
|
end
|
125
159
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
160
|
+
# Get the port for a given process and offset
|
161
|
+
#
|
162
|
+
# @param [Foreman::Process] process A +Process+ associated with this engine
|
163
|
+
# @param [Fixnum] instance The instance of the process
|
164
|
+
#
|
165
|
+
# @returns [Fixnum] port The port to use for this instance of this process
|
166
|
+
#
|
167
|
+
def port_for(process, instance, base=nil)
|
168
|
+
if base
|
169
|
+
base + (@processes.index(process.process) * 100) + (instance - 1)
|
170
|
+
else
|
171
|
+
base_port + (@processes.index(process) * 100) + (instance - 1)
|
137
172
|
end
|
138
173
|
end
|
139
174
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
175
|
+
# Get the base port for this foreman instance
|
176
|
+
#
|
177
|
+
# @returns [Fixnum] port The base port
|
178
|
+
#
|
179
|
+
def base_port
|
180
|
+
(options[:port] || env["PORT"] || ENV["PORT"] || 5000).to_i
|
146
181
|
end
|
147
182
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
|
152
|
-
output += $stdout.color(:reset)
|
153
|
-
output += message.chomp
|
154
|
-
puts output
|
183
|
+
# deprecated
|
184
|
+
def environment
|
185
|
+
env
|
155
186
|
end
|
156
187
|
|
157
|
-
|
158
|
-
@output_mutex.synchronize do
|
159
|
-
$stdout.print message
|
160
|
-
end
|
161
|
-
end
|
188
|
+
private
|
162
189
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
190
|
+
### Engine API ######################################################
|
191
|
+
|
192
|
+
def startup
|
193
|
+
raise TypeError, "must use a subclass of Foreman::Engine"
|
167
194
|
end
|
168
195
|
|
169
|
-
def
|
170
|
-
|
171
|
-
longest = procfile.process_names.map { |name| name.length }.sort.last
|
172
|
-
longest = 6 if longest < 6 # system
|
173
|
-
longest
|
174
|
-
end
|
196
|
+
def output(name, data)
|
197
|
+
raise TypeError, "must use a subclass of Foreman::Engine"
|
175
198
|
end
|
176
199
|
|
177
|
-
def
|
178
|
-
|
200
|
+
def shutdown
|
201
|
+
raise TypeError, "must use a subclass of Foreman::Engine"
|
179
202
|
end
|
180
203
|
|
181
|
-
|
182
|
-
|
204
|
+
## Helpers ##########################################################
|
205
|
+
|
206
|
+
def create_pipe
|
207
|
+
IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
|
183
208
|
end
|
184
209
|
|
185
|
-
def
|
186
|
-
|
210
|
+
def name_for(pid)
|
211
|
+
process, index = @running[pid]
|
212
|
+
[ @names[process], index.to_s ].compact.join(".")
|
187
213
|
end
|
188
214
|
|
189
|
-
def
|
190
|
-
|
215
|
+
def parse_formation(formation)
|
216
|
+
pairs = formation.to_s.gsub(/\s/, "").split(",")
|
217
|
+
|
218
|
+
pairs.inject(Hash.new(0)) do |ax, pair|
|
219
|
+
process, amount = pair.split("=")
|
220
|
+
process == "all" ? ax.default = amount.to_i : ax[process] = amount.to_i
|
221
|
+
ax
|
222
|
+
end
|
191
223
|
end
|
192
224
|
|
193
|
-
def
|
194
|
-
@
|
225
|
+
def output_with_mutex(name, message)
|
226
|
+
@mutex.synchronize do
|
227
|
+
output name, message
|
228
|
+
end
|
195
229
|
end
|
196
230
|
|
197
|
-
def
|
198
|
-
|
231
|
+
def system(message)
|
232
|
+
output_with_mutex "system", message
|
199
233
|
end
|
200
234
|
|
201
|
-
def
|
202
|
-
|
203
|
-
|
235
|
+
def termination_message_for(status)
|
236
|
+
if status.exited?
|
237
|
+
"exited with code #{status.exitstatus}"
|
238
|
+
elsif status.signaled?
|
239
|
+
"terminated by SIG#{Signal.list.invert[status.termsig]}"
|
240
|
+
else
|
241
|
+
"died a mysterious death"
|
204
242
|
end
|
205
243
|
end
|
206
244
|
|
207
|
-
def
|
208
|
-
|
245
|
+
def flush_reader(reader)
|
246
|
+
until reader.eof?
|
247
|
+
data = reader.gets
|
248
|
+
output_with_mutex name_for(@readers.key(reader)), data
|
249
|
+
end
|
209
250
|
end
|
210
251
|
|
211
|
-
|
212
|
-
environment = {}
|
252
|
+
## Engine ###########################################################
|
213
253
|
|
214
|
-
|
215
|
-
|
216
|
-
|
254
|
+
def spawn_processes
|
255
|
+
@processes.each do |process|
|
256
|
+
1.upto(formation[@names[process]]) do |n|
|
257
|
+
reader, writer = create_pipe
|
258
|
+
begin
|
259
|
+
pid = process.run(:output => writer, :env => { "PORT" => port_for(process, n).to_s })
|
260
|
+
writer.puts "started with pid #{pid}"
|
261
|
+
rescue Errno::ENOENT
|
262
|
+
writer.puts "unknown command: #{process.command}"
|
263
|
+
end
|
264
|
+
@running[pid] = [process, n]
|
265
|
+
@readers[pid] = reader
|
266
|
+
end
|
217
267
|
end
|
268
|
+
end
|
218
269
|
|
219
|
-
|
270
|
+
def watch_for_output
|
271
|
+
Thread.new do
|
272
|
+
begin
|
273
|
+
loop do
|
274
|
+
(IO.select(@readers.values).first || []).each do |reader|
|
275
|
+
data = reader.gets
|
276
|
+
output_with_mutex name_for(@readers.invert[reader]), data
|
277
|
+
end
|
278
|
+
end
|
279
|
+
rescue Exception => ex
|
280
|
+
puts ex.message
|
281
|
+
puts ex.backtrace
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def watch_for_termination
|
287
|
+
pid, status = Process.wait2
|
288
|
+
output_with_mutex name_for(pid), termination_message_for(status)
|
289
|
+
@running.delete(pid)
|
290
|
+
yield if block_given?
|
291
|
+
pid
|
292
|
+
rescue Errno::ECHILD
|
220
293
|
end
|
221
294
|
|
222
|
-
def
|
223
|
-
|
224
|
-
|
295
|
+
def terminate_gracefully
|
296
|
+
return if @terminating
|
297
|
+
@terminating = true
|
298
|
+
if Foreman.windows?
|
299
|
+
system "sending SIGKILL to all processes"
|
300
|
+
killall "SIGKILL"
|
301
|
+
else
|
302
|
+
system "sending SIGTERM to all processes"
|
303
|
+
killall "SIGTERM"
|
304
|
+
end
|
305
|
+
Timeout.timeout(5) do
|
306
|
+
watch_for_termination while @running.length > 0
|
307
|
+
end
|
308
|
+
rescue Timeout::Error
|
309
|
+
system "sending SIGKILL to all processes"
|
310
|
+
killall "SIGKILL"
|
225
311
|
end
|
226
312
|
|
227
313
|
end
|