foreman 0.26.1 → 0.40.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.
- data/README.md +40 -0
- data/bin/foreman-runner +36 -0
- data/data/example/Procfile +1 -0
- data/data/example/utf8 +11 -0
- data/data/export/bluepill/master.pill.erb +3 -3
- data/lib/foreman/cli.rb +35 -31
- data/lib/foreman/engine.rb +109 -82
- data/lib/foreman/export/base.rb +12 -5
- data/lib/foreman/export/bluepill.rb +5 -7
- data/lib/foreman/export/inittab.rb +11 -13
- data/lib/foreman/export/runit.rb +24 -25
- data/lib/foreman/export/upstart.rb +9 -11
- data/lib/foreman/export.rb +21 -0
- data/lib/foreman/helpers.rb +45 -0
- data/lib/foreman/process.rb +88 -6
- data/lib/foreman/procfile.rb +8 -7
- data/lib/foreman/procfile_entry.rb +22 -0
- data/lib/foreman/utils.rb +4 -1
- data/lib/foreman/version.rb +1 -1
- data/lib/foreman.rb +12 -1
- data/man/foreman.1 +17 -3
- data/spec/foreman/cli_spec.rb +96 -7
- data/spec/foreman/engine_spec.rb +52 -31
- data/spec/foreman/export/base_spec.rb +22 -0
- data/spec/foreman/export/bluepill_spec.rb +23 -7
- data/spec/foreman/export/inittab_spec.rb +40 -0
- data/spec/foreman/export/runit_spec.rb +18 -12
- data/spec/foreman/export/upstart_spec.rb +40 -8
- data/spec/foreman/export_spec.rb +22 -0
- data/spec/foreman/helpers_spec.rb +26 -0
- data/spec/foreman/process_spec.rb +131 -2
- data/spec/foreman_spec.rb +5 -0
- data/spec/helper_spec.rb +18 -0
- data/spec/resources/bin/utf8 +2 -0
- data/spec/resources/export/bluepill/app-concurrency.pill +47 -0
- data/spec/resources/export/bluepill/app.pill +1 -22
- data/spec/resources/export/inittab/inittab.concurrency +4 -0
- data/spec/resources/export/inittab/inittab.default +4 -0
- data/spec/spec_helper.rb +36 -4
- metadata +23 -11
- data/README.markdown +0 -49
data/README.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Foreman
|
|
2
|
+
|
|
3
|
+
Manage Procfile-based applications
|
|
4
|
+
|
|
5
|
+
<table>
|
|
6
|
+
<tr>
|
|
7
|
+
<th>If you have...</th>
|
|
8
|
+
<th>Install with...</th>
|
|
9
|
+
</tr>
|
|
10
|
+
<tr>
|
|
11
|
+
<td>Ruby (MRI, JRuby, Windows)</td>
|
|
12
|
+
<td><pre>$ gem install foreman</pre></td>
|
|
13
|
+
</tr>
|
|
14
|
+
<tr>
|
|
15
|
+
<td>Mac OS X</td>
|
|
16
|
+
<td><a href="http://assets.foreman.io/foreman/foreman.pkg">foreman.pkg</a></td>
|
|
17
|
+
</tr>
|
|
18
|
+
</table>
|
|
19
|
+
|
|
20
|
+
## Getting Started
|
|
21
|
+
|
|
22
|
+
* http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
|
|
23
|
+
|
|
24
|
+
## Documentation
|
|
25
|
+
|
|
26
|
+
* [man page](http://ddollar.github.com/foreman)
|
|
27
|
+
* [wiki](http://github.com/ddollar/foreman/wiki)
|
|
28
|
+
* [changelog](https://github.com/ddollar/foreman/blob/master/Changelog.md)
|
|
29
|
+
|
|
30
|
+
## Authors
|
|
31
|
+
|
|
32
|
+
#### Created and maintained by
|
|
33
|
+
David Dollar
|
|
34
|
+
|
|
35
|
+
#### Patches contributed by
|
|
36
|
+
[Contributor List](https://github.com/ddollar/foreman/contributors)
|
|
37
|
+
|
|
38
|
+
## License
|
|
39
|
+
|
|
40
|
+
MIT
|
data/bin/foreman-runner
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
#
|
|
3
|
+
#/ Usage: foreman-runner [-d <dir>] <command>
|
|
4
|
+
#/
|
|
5
|
+
#/ Run a command with exec, optionally changing directory first
|
|
6
|
+
|
|
7
|
+
set -e
|
|
8
|
+
|
|
9
|
+
error() {
|
|
10
|
+
echo $@ >&2
|
|
11
|
+
exit 1
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
usage() {
|
|
15
|
+
cat $0 | grep '^#/' | cut -c4-
|
|
16
|
+
exit
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
while getopts ":hd:" OPT; do
|
|
20
|
+
case $OPT in
|
|
21
|
+
d) cd "$OPTARG" ;;
|
|
22
|
+
h) usage ;;
|
|
23
|
+
\?) error "invalid option: -$OPTARG" ;;
|
|
24
|
+
:) error "option -$OPTARG requires an argument" ;;
|
|
25
|
+
esac
|
|
26
|
+
done
|
|
27
|
+
|
|
28
|
+
shift $((OPTIND-1))
|
|
29
|
+
|
|
30
|
+
command=$1
|
|
31
|
+
|
|
32
|
+
if [ -z "$1" ]; then
|
|
33
|
+
usage
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
exec $1
|
data/data/example/Procfile
CHANGED
data/data/example/utf8
ADDED
|
@@ -3,9 +3,9 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
|
|
|
3
3
|
app.uid = "<%= user %>"
|
|
4
4
|
app.gid = "<%= user %>"
|
|
5
5
|
|
|
6
|
-
<% engine.
|
|
6
|
+
<% engine.procfile.entries.each do |process| %>
|
|
7
7
|
<% 1.upto(concurrency[process.name]) do |num| %>
|
|
8
|
-
<% port = engine.port_for(process, num,
|
|
8
|
+
<% port = engine.port_for(process, num, self.port) %>
|
|
9
9
|
app.process("<%= process.name %>-<%=num%>") do |process|
|
|
10
10
|
process.start_command = "<%= process.command.gsub("$PORT", port.to_s) %>"
|
|
11
11
|
|
|
@@ -19,7 +19,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
|
|
|
19
19
|
process.monitor_children do |children|
|
|
20
20
|
children.stop_command "kill -QUIT {{PID}}"
|
|
21
21
|
end
|
|
22
|
-
|
|
22
|
+
|
|
23
23
|
process.group = "<%= app %>-<%= process.name %>"
|
|
24
24
|
end
|
|
25
25
|
<% end %>
|
data/lib/foreman/cli.rb
CHANGED
|
@@ -1,27 +1,36 @@
|
|
|
1
1
|
require "foreman"
|
|
2
|
+
require "foreman/helpers"
|
|
2
3
|
require "foreman/engine"
|
|
3
4
|
require "foreman/export"
|
|
4
5
|
require "thor"
|
|
5
6
|
require "yaml"
|
|
6
7
|
|
|
7
8
|
class Foreman::CLI < Thor
|
|
9
|
+
include Foreman::Helpers
|
|
8
10
|
|
|
9
11
|
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
|
|
10
12
|
|
|
11
|
-
desc "start [PROCESS]", "Start the application
|
|
13
|
+
desc "start [PROCESS]", "Start the application (or a specific PROCESS)"
|
|
14
|
+
|
|
15
|
+
class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
|
|
16
|
+
class_option :app_root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
|
|
12
17
|
|
|
13
18
|
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
|
|
14
19
|
method_option :port, :type => :numeric, :aliases => "-p"
|
|
15
20
|
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
|
|
16
21
|
|
|
22
|
+
class << self
|
|
23
|
+
# Hackery. Take the run method away from Thor so that we can redefine it.
|
|
24
|
+
def is_thor_reserved_word?(word, type)
|
|
25
|
+
return false if word == 'run'
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
17
30
|
def start(process=nil)
|
|
18
31
|
check_procfile!
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
engine.execute(process)
|
|
22
|
-
else
|
|
23
|
-
engine.start
|
|
24
|
-
end
|
|
32
|
+
engine.options[:concurrency] = "#{process}=1" if process
|
|
33
|
+
engine.start
|
|
25
34
|
end
|
|
26
35
|
|
|
27
36
|
desc "export FORMAT LOCATION", "Export the application to another process management format"
|
|
@@ -32,22 +41,12 @@ class Foreman::CLI < Thor
|
|
|
32
41
|
method_option :port, :type => :numeric, :aliases => "-p"
|
|
33
42
|
method_option :user, :type => :string, :aliases => "-u"
|
|
34
43
|
method_option :template, :type => :string, :aliases => "-t"
|
|
35
|
-
method_option :concurrency, :type => :string, :aliases => "-c",
|
|
36
|
-
:banner => '"alpha=5,bar=3"'
|
|
44
|
+
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
|
|
37
45
|
|
|
38
46
|
def export(format, location=nil)
|
|
39
47
|
check_procfile!
|
|
40
|
-
|
|
41
|
-
formatter
|
|
42
|
-
when "inittab" then Foreman::Export::Inittab
|
|
43
|
-
when "upstart" then Foreman::Export::Upstart
|
|
44
|
-
when "bluepill" then Foreman::Export::Bluepill
|
|
45
|
-
when "runit" then Foreman::Export::Runit
|
|
46
|
-
else error "Unknown export format: #{format}."
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
formatter.new(engine).export(location, options)
|
|
50
|
-
|
|
48
|
+
formatter = Foreman::Export.formatter(format)
|
|
49
|
+
formatter.new(location, engine, options).export
|
|
51
50
|
rescue Foreman::Export::Exception => ex
|
|
52
51
|
error ex.message
|
|
53
52
|
end
|
|
@@ -55,8 +54,22 @@ class Foreman::CLI < Thor
|
|
|
55
54
|
desc "check", "Validate your application's Procfile"
|
|
56
55
|
|
|
57
56
|
def check
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
check_procfile!
|
|
58
|
+
error "no processes defined" unless engine.procfile.entries.length > 0
|
|
59
|
+
puts "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
desc "run COMMAND", "Run a command using your application's environment"
|
|
63
|
+
|
|
64
|
+
def run(*args)
|
|
65
|
+
engine.apply_environment!
|
|
66
|
+
begin
|
|
67
|
+
exec args.join(" ")
|
|
68
|
+
rescue Errno::EACCES
|
|
69
|
+
error "not executable: #{args.first}"
|
|
70
|
+
rescue Errno::ENOENT
|
|
71
|
+
error "command not found: #{args.first}"
|
|
72
|
+
end
|
|
60
73
|
end
|
|
61
74
|
|
|
62
75
|
private ######################################################################
|
|
@@ -73,24 +86,15 @@ private ######################################################################
|
|
|
73
86
|
options[:procfile] || "Procfile"
|
|
74
87
|
end
|
|
75
88
|
|
|
76
|
-
def display(message)
|
|
77
|
-
puts message
|
|
78
|
-
end
|
|
79
|
-
|
|
80
89
|
def error(message)
|
|
81
90
|
puts "ERROR: #{message}"
|
|
82
91
|
exit 1
|
|
83
92
|
end
|
|
84
93
|
|
|
85
|
-
def procfile_exists?(procfile)
|
|
86
|
-
File.exist?(procfile)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
94
|
def options
|
|
90
95
|
original_options = super
|
|
91
96
|
return original_options unless File.exists?(".foreman")
|
|
92
97
|
defaults = YAML::load_file(".foreman") || {}
|
|
93
98
|
Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
|
|
94
99
|
end
|
|
95
|
-
|
|
96
100
|
end
|
data/lib/foreman/engine.rb
CHANGED
|
@@ -2,146 +2,151 @@ require "foreman"
|
|
|
2
2
|
require "foreman/process"
|
|
3
3
|
require "foreman/procfile"
|
|
4
4
|
require "foreman/utils"
|
|
5
|
-
require "pty"
|
|
6
5
|
require "tempfile"
|
|
7
6
|
require "timeout"
|
|
8
7
|
require "term/ansicolor"
|
|
9
8
|
require "fileutils"
|
|
9
|
+
require "thread"
|
|
10
10
|
|
|
11
11
|
class Foreman::Engine
|
|
12
12
|
|
|
13
|
+
attr_reader :environment
|
|
13
14
|
attr_reader :procfile
|
|
14
15
|
attr_reader :directory
|
|
15
|
-
attr_reader :environment
|
|
16
16
|
attr_reader :options
|
|
17
17
|
|
|
18
18
|
extend Term::ANSIColor
|
|
19
19
|
|
|
20
|
-
COLORS = [ cyan, yellow, green, magenta, red
|
|
20
|
+
COLORS = [ cyan, yellow, green, magenta, red, blue,
|
|
21
|
+
intense_cyan, intense_yellow, intense_green, intense_magenta,
|
|
22
|
+
intense_red, intense_blue ]
|
|
21
23
|
|
|
22
24
|
def initialize(procfile, options={})
|
|
23
25
|
@procfile = Foreman::Procfile.new(procfile)
|
|
24
|
-
@directory = File.expand_path(File.dirname(procfile))
|
|
25
|
-
@options = options
|
|
26
|
+
@directory = options[:app_root] || File.expand_path(File.dirname(procfile))
|
|
27
|
+
@options = options.dup
|
|
26
28
|
@environment = read_environment_files(options[:env])
|
|
29
|
+
@output_mutex = Mutex.new
|
|
27
30
|
end
|
|
28
31
|
|
|
29
32
|
def start
|
|
30
33
|
proctitle "ruby: foreman master"
|
|
31
|
-
termtitle "#{File.basename(@directory)} - foreman
|
|
32
|
-
|
|
33
|
-
processes.each do |process|
|
|
34
|
-
process.color = next_color
|
|
35
|
-
fork process
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
|
39
|
-
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
|
40
|
-
|
|
41
|
-
watch_for_termination
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def execute(name)
|
|
45
|
-
error "no such process: #{name}" unless process = procfile[name]
|
|
46
|
-
process.color = next_color
|
|
47
|
-
fork process
|
|
34
|
+
termtitle "#{File.basename(@directory)} - foreman"
|
|
48
35
|
|
|
49
36
|
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
|
50
37
|
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
|
51
38
|
|
|
39
|
+
assign_colors
|
|
40
|
+
spawn_processes
|
|
41
|
+
watch_for_output
|
|
52
42
|
watch_for_termination
|
|
53
43
|
end
|
|
54
44
|
|
|
55
|
-
def processes
|
|
56
|
-
procfile.processes
|
|
57
|
-
end
|
|
58
|
-
|
|
59
45
|
def port_for(process, num, base_port=nil)
|
|
60
46
|
base_port ||= 5000
|
|
61
47
|
offset = procfile.process_names.index(process.name) * 100
|
|
62
48
|
base_port.to_i + offset + num - 1
|
|
63
49
|
end
|
|
64
50
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def fork(process)
|
|
68
|
-
concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
|
|
69
|
-
|
|
70
|
-
1.upto(concurrency[process.name]) do |num|
|
|
71
|
-
fork_individual(process, num, port_for(process, num, @options[:port]))
|
|
72
|
-
end
|
|
51
|
+
def apply_environment!
|
|
52
|
+
environment.each { |k,v| ENV[k] = v }
|
|
73
53
|
end
|
|
74
54
|
|
|
75
|
-
|
|
76
|
-
@environment.each { |k,v| ENV[k] = v }
|
|
55
|
+
private ######################################################################
|
|
77
56
|
|
|
78
|
-
|
|
79
|
-
|
|
57
|
+
def spawn_processes
|
|
58
|
+
concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
|
|
80
59
|
|
|
81
|
-
|
|
82
|
-
|
|
60
|
+
procfile.entries.each do |entry|
|
|
61
|
+
reader, writer = (IO.method(:pipe).arity == 0 ? IO.pipe : IO.pipe("BINARY"))
|
|
62
|
+
entry.spawn(concurrency[entry.name], writer, @directory, @environment, port_for(entry, 1, base_port)).each do |process|
|
|
63
|
+
running_processes[process.pid] = process
|
|
64
|
+
readers[process] = reader
|
|
65
|
+
end
|
|
83
66
|
end
|
|
84
|
-
|
|
85
|
-
info "started with pid #{pid}", process
|
|
86
|
-
running_processes[pid] = process
|
|
87
67
|
end
|
|
88
68
|
|
|
89
|
-
def
|
|
90
|
-
|
|
91
|
-
trap("SIGINT", "IGNORE")
|
|
92
|
-
|
|
93
|
-
begin
|
|
94
|
-
Dir.chdir directory do
|
|
95
|
-
PTY.spawn(process.command) do |stdin, stdout, pid|
|
|
96
|
-
trap("SIGTERM") { Process.kill("SIGTERM", pid) }
|
|
97
|
-
until stdin.eof?
|
|
98
|
-
info stdin.gets, process
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
rescue PTY::ChildExited, Interrupt, Errno::EIO, Errno::ENOENT
|
|
103
|
-
begin
|
|
104
|
-
info "process exiting", process
|
|
105
|
-
rescue Interrupt
|
|
106
|
-
end
|
|
107
|
-
end
|
|
69
|
+
def base_port
|
|
70
|
+
options[:port] || 5000
|
|
108
71
|
end
|
|
109
72
|
|
|
110
73
|
def kill_all(signal="SIGTERM")
|
|
111
74
|
running_processes.each do |pid, process|
|
|
112
|
-
|
|
75
|
+
info "sending #{signal} to pid #{pid}"
|
|
76
|
+
process.kill signal
|
|
113
77
|
end
|
|
114
78
|
end
|
|
115
79
|
|
|
116
80
|
def terminate_gracefully
|
|
81
|
+
return if @terminating
|
|
82
|
+
@terminating = true
|
|
117
83
|
info "sending SIGTERM to all processes"
|
|
118
84
|
kill_all "SIGTERM"
|
|
119
|
-
Timeout.timeout(
|
|
85
|
+
Timeout.timeout(5) do
|
|
86
|
+
while running_processes.length > 0
|
|
87
|
+
pid, status = Process.wait2
|
|
88
|
+
process = running_processes.delete(pid)
|
|
89
|
+
info "process terminated", process.name
|
|
90
|
+
end
|
|
91
|
+
end
|
|
120
92
|
rescue Timeout::Error
|
|
121
93
|
info "sending SIGKILL to all processes"
|
|
122
94
|
kill_all "SIGKILL"
|
|
123
95
|
end
|
|
124
96
|
|
|
97
|
+
def poll_readers
|
|
98
|
+
rs, ws = IO.select(readers.values, [], [], 1)
|
|
99
|
+
(rs || []).each do |r|
|
|
100
|
+
data = r.gets
|
|
101
|
+
next unless data
|
|
102
|
+
data.force_encoding("BINARY") if data.respond_to?(:force_encoding)
|
|
103
|
+
ps, message = data.split(",", 2)
|
|
104
|
+
color = colors[ps.split(".").first]
|
|
105
|
+
info message, ps, color
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def watch_for_output
|
|
110
|
+
Thread.new do
|
|
111
|
+
require "win32console" if Foreman.windows?
|
|
112
|
+
begin
|
|
113
|
+
loop do
|
|
114
|
+
poll_readers
|
|
115
|
+
end
|
|
116
|
+
rescue Exception => ex
|
|
117
|
+
puts ex.message
|
|
118
|
+
puts ex.backtrace
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
125
123
|
def watch_for_termination
|
|
126
124
|
pid, status = Process.wait2
|
|
127
125
|
process = running_processes.delete(pid)
|
|
128
|
-
info "process terminated", process
|
|
126
|
+
info "process terminated", process.name
|
|
129
127
|
terminate_gracefully
|
|
130
|
-
kill_all
|
|
131
128
|
rescue Errno::ECHILD
|
|
132
129
|
end
|
|
133
130
|
|
|
134
|
-
def info(message,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
def info(message, name="system", color=Term::ANSIColor.white)
|
|
132
|
+
output = ""
|
|
133
|
+
output += color
|
|
134
|
+
output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
|
|
135
|
+
output += Term::ANSIColor.reset
|
|
136
|
+
output += message.chomp
|
|
137
|
+
puts output
|
|
140
138
|
end
|
|
141
139
|
|
|
142
|
-
def
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
def print(message=nil)
|
|
141
|
+
@output_mutex.synchronize do
|
|
142
|
+
$stdout.print message
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def puts(message=nil)
|
|
147
|
+
@output_mutex.synchronize do
|
|
148
|
+
$stdout.puts message
|
|
149
|
+
end
|
|
145
150
|
end
|
|
146
151
|
|
|
147
152
|
def longest_process_name
|
|
@@ -152,9 +157,8 @@ private ######################################################################
|
|
|
152
157
|
end
|
|
153
158
|
end
|
|
154
159
|
|
|
155
|
-
def pad_process_name(
|
|
156
|
-
name
|
|
157
|
-
name.ljust(longest_process_name + 3) # add 3 for process number padding
|
|
160
|
+
def pad_process_name(name="system")
|
|
161
|
+
name.to_s.ljust(longest_process_name + 3) # add 3 for process number padding
|
|
158
162
|
end
|
|
159
163
|
|
|
160
164
|
def proctitle(title)
|
|
@@ -162,17 +166,36 @@ private ######################################################################
|
|
|
162
166
|
end
|
|
163
167
|
|
|
164
168
|
def termtitle(title)
|
|
165
|
-
printf("\033]0;#{title}\007")
|
|
169
|
+
printf("\033]0;#{title}\007") unless Foreman.windows?
|
|
166
170
|
end
|
|
167
171
|
|
|
168
172
|
def running_processes
|
|
169
173
|
@running_processes ||= {}
|
|
170
174
|
end
|
|
171
175
|
|
|
176
|
+
def readers
|
|
177
|
+
@readers ||= {}
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def colors
|
|
181
|
+
@colors ||= {}
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def assign_colors
|
|
185
|
+
procfile.entries.each do |entry|
|
|
186
|
+
colors[entry.name] = next_color
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def process_by_reader(reader)
|
|
191
|
+
readers.invert[reader]
|
|
192
|
+
end
|
|
193
|
+
|
|
172
194
|
def next_color
|
|
173
195
|
@current_color ||= -1
|
|
174
196
|
@current_color += 1
|
|
175
|
-
@current_color
|
|
197
|
+
@current_color = 0 if COLORS.length < @current_color
|
|
198
|
+
COLORS[@current_color]
|
|
176
199
|
end
|
|
177
200
|
|
|
178
201
|
def read_environment_files(filenames)
|
|
@@ -192,10 +215,14 @@ private ######################################################################
|
|
|
192
215
|
|
|
193
216
|
File.read(filename).split("\n").inject({}) do |hash, line|
|
|
194
217
|
if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
|
|
195
|
-
|
|
218
|
+
key, val = [$1, $2]
|
|
219
|
+
case val
|
|
220
|
+
when /\A'(.*)'\z/ then hash[key] = $1
|
|
221
|
+
when /\A"(.*)"\z/ then hash[key] = $1.gsub(/\\(.)/, '\1')
|
|
222
|
+
else hash[key] = val
|
|
223
|
+
end
|
|
196
224
|
end
|
|
197
225
|
hash
|
|
198
226
|
end
|
|
199
227
|
end
|
|
200
|
-
|
|
201
228
|
end
|
data/lib/foreman/export/base.rb
CHANGED
|
@@ -3,10 +3,17 @@ require "foreman/utils"
|
|
|
3
3
|
|
|
4
4
|
class Foreman::Export::Base
|
|
5
5
|
|
|
6
|
-
attr_reader :engine
|
|
7
|
-
|
|
8
|
-
def initialize(engine)
|
|
9
|
-
@
|
|
6
|
+
attr_reader :location, :engine, :app, :log, :port, :user, :template, :concurrency
|
|
7
|
+
|
|
8
|
+
def initialize(location, engine, options={})
|
|
9
|
+
@location = location
|
|
10
|
+
@engine = engine
|
|
11
|
+
@app = options[:app]
|
|
12
|
+
@log = options[:log]
|
|
13
|
+
@port = options[:port]
|
|
14
|
+
@user = options[:user]
|
|
15
|
+
@template = options[:template]
|
|
16
|
+
@concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
|
10
17
|
end
|
|
11
18
|
|
|
12
19
|
def export
|
|
@@ -26,7 +33,7 @@ private ######################################################################
|
|
|
26
33
|
def export_template(exporter, file, template_root)
|
|
27
34
|
if template_root && File.exist?(file_path = File.join(template_root, file))
|
|
28
35
|
File.read(file_path)
|
|
29
|
-
elsif File.exist?(file_path = File.join("~/.foreman/templates", file))
|
|
36
|
+
elsif File.exist?(file_path = File.expand_path(File.join("~/.foreman/templates", file)))
|
|
30
37
|
File.read(file_path)
|
|
31
38
|
else
|
|
32
39
|
File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
|
|
@@ -3,23 +3,21 @@ require "foreman/export"
|
|
|
3
3
|
|
|
4
4
|
class Foreman::Export::Bluepill < Foreman::Export::Base
|
|
5
5
|
|
|
6
|
-
def export
|
|
6
|
+
def export
|
|
7
7
|
error("Must specify a location") unless location
|
|
8
8
|
|
|
9
9
|
FileUtils.mkdir_p location
|
|
10
10
|
|
|
11
|
-
app =
|
|
12
|
-
user =
|
|
13
|
-
log_root =
|
|
14
|
-
template_root =
|
|
11
|
+
app = self.app || File.basename(engine.directory)
|
|
12
|
+
user = self.user || app
|
|
13
|
+
log_root = self.log || "/var/log/#{app}"
|
|
14
|
+
template_root = self.template
|
|
15
15
|
|
|
16
16
|
Dir["#{location}/#{app}.pill"].each do |file|
|
|
17
17
|
say "cleaning up: #{file}"
|
|
18
18
|
FileUtils.rm(file)
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
|
22
|
-
|
|
23
21
|
master_template = export_template("bluepill", "master.pill.erb", template_root)
|
|
24
22
|
master_config = ERB.new(master_template).result(binding)
|
|
25
23
|
write_file "#{location}/#{app}.pill", master_config
|
|
@@ -2,20 +2,18 @@ require "foreman/export"
|
|
|
2
2
|
|
|
3
3
|
class Foreman::Export::Inittab < Foreman::Export::Base
|
|
4
4
|
|
|
5
|
-
def export
|
|
6
|
-
app =
|
|
7
|
-
user =
|
|
8
|
-
log_root =
|
|
9
|
-
|
|
10
|
-
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
|
5
|
+
def export
|
|
6
|
+
app = self.app || File.basename(engine.directory)
|
|
7
|
+
user = self.user || app
|
|
8
|
+
log_root = self.log || "/var/log/#{app}"
|
|
11
9
|
|
|
12
10
|
inittab = []
|
|
13
11
|
inittab << "# ----- foreman #{app} processes -----"
|
|
14
12
|
|
|
15
|
-
engine.
|
|
16
|
-
1.upto(concurrency[process.name]) do |num|
|
|
13
|
+
engine.procfile.entries.inject(1) do |index, process|
|
|
14
|
+
1.upto(self.concurrency[process.name]) do |num|
|
|
17
15
|
id = app.slice(0, 2).upcase + sprintf("%02d", index)
|
|
18
|
-
port = engine.port_for(process, num,
|
|
16
|
+
port = engine.port_for(process, num, self.port)
|
|
19
17
|
inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log_root}/#{process.name}-#{num}.log 2>&1'"
|
|
20
18
|
index += 1
|
|
21
19
|
end
|
|
@@ -26,12 +24,12 @@ class Foreman::Export::Inittab < Foreman::Export::Base
|
|
|
26
24
|
|
|
27
25
|
inittab = inittab.join("\n") + "\n"
|
|
28
26
|
|
|
29
|
-
if
|
|
27
|
+
if location == "-"
|
|
28
|
+
puts inittab
|
|
29
|
+
else
|
|
30
30
|
FileUtils.mkdir_p(log_root) rescue error "could not create #{log_root}"
|
|
31
31
|
FileUtils.chown(user, nil, log_root) rescue error "could not chown #{log_root} to #{user}"
|
|
32
|
-
write_file(
|
|
33
|
-
else
|
|
34
|
-
puts inittab
|
|
32
|
+
write_file(location, inittab)
|
|
35
33
|
end
|
|
36
34
|
end
|
|
37
35
|
|