foreman 0.22.0 → 0.24.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/data/example/Procfile +1 -1
- data/data/export/bluepill/master.pill.erb +2 -2
- data/lib/foreman/cli.rb +2 -3
- data/lib/foreman/engine.rb +43 -83
- data/lib/foreman/export/upstart.rb +1 -1
- data/lib/foreman/procfile.rb +37 -0
- data/lib/foreman/version.rb +1 -1
- data/man/foreman.1 +3 -3
- data/spec/foreman/engine_spec.rb +18 -22
- metadata +9 -8
data/data/example/Procfile
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
ticker: ruby ./ticker $PORT
|
|
2
|
-
error
|
|
2
|
+
error: ruby ./error
|
|
@@ -3,7 +3,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
|
|
|
3
3
|
app.uid = "<%= user %>"
|
|
4
4
|
app.gid = "<%= user %>"
|
|
5
5
|
|
|
6
|
-
<% engine.processes.
|
|
6
|
+
<% engine.processes.each do |process| %>
|
|
7
7
|
<% 1.upto(concurrency[process.name]) do |num| %>
|
|
8
8
|
<% port = engine.port_for(process, num, options[:port]) %>
|
|
9
9
|
app.process("<%= process.name %>-<%=num%>") do |process|
|
|
@@ -24,4 +24,4 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
|
|
|
24
24
|
end
|
|
25
25
|
<% end %>
|
|
26
26
|
<% end %>
|
|
27
|
-
end
|
|
27
|
+
end
|
data/lib/foreman/cli.rb
CHANGED
|
@@ -53,9 +53,8 @@ class Foreman::CLI < Thor
|
|
|
53
53
|
desc "check", "Validate your application's Procfile"
|
|
54
54
|
|
|
55
55
|
def check
|
|
56
|
-
processes
|
|
57
|
-
|
|
58
|
-
display "valid procfile detected (#{processes.join(', ')})"
|
|
56
|
+
error "no processes defined" unless engine.processes.length > 0
|
|
57
|
+
display "valid procfile detected (#{engine.processes.map(&:name).join(', ')})"
|
|
59
58
|
end
|
|
60
59
|
|
|
61
60
|
private ######################################################################
|
data/lib/foreman/engine.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require "foreman"
|
|
2
2
|
require "foreman/process"
|
|
3
|
+
require "foreman/procfile"
|
|
3
4
|
require "foreman/utils"
|
|
4
5
|
require "pty"
|
|
5
6
|
require "tempfile"
|
|
@@ -19,45 +20,17 @@ class Foreman::Engine
|
|
|
19
20
|
COLORS = [ cyan, yellow, green, magenta, red ]
|
|
20
21
|
|
|
21
22
|
def initialize(procfile, options={})
|
|
22
|
-
@procfile =
|
|
23
|
+
@procfile = Foreman::Procfile.new(procfile)
|
|
23
24
|
@directory = File.expand_path(File.dirname(procfile))
|
|
24
25
|
@options = options
|
|
25
|
-
@environment =
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def processes
|
|
29
|
-
@processes ||= begin
|
|
30
|
-
@order = []
|
|
31
|
-
procfile.split("\n").inject({}) do |hash, line|
|
|
32
|
-
next hash if line.strip == ""
|
|
33
|
-
name, command = line.split(/\s*:\s+/, 2)
|
|
34
|
-
unless command
|
|
35
|
-
warn_deprecated_procfile!
|
|
36
|
-
name, command = line.split(/ +/, 2)
|
|
37
|
-
end
|
|
38
|
-
process = Foreman::Process.new(name, command)
|
|
39
|
-
process.color = next_color
|
|
40
|
-
@order << process.name
|
|
41
|
-
hash.update(process.name => process)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def process_order
|
|
47
|
-
processes
|
|
48
|
-
@order.uniq
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def processes_in_order
|
|
52
|
-
process_order.map do |name|
|
|
53
|
-
[name, processes[name]]
|
|
54
|
-
end
|
|
26
|
+
@environment = read_environment_files(options[:env])
|
|
55
27
|
end
|
|
56
28
|
|
|
57
29
|
def start
|
|
58
30
|
proctitle "ruby: foreman master"
|
|
59
31
|
|
|
60
|
-
|
|
32
|
+
processes.each do |process|
|
|
33
|
+
process.color = next_color
|
|
61
34
|
fork process
|
|
62
35
|
end
|
|
63
36
|
|
|
@@ -68,8 +41,7 @@ class Foreman::Engine
|
|
|
68
41
|
end
|
|
69
42
|
|
|
70
43
|
def execute(name)
|
|
71
|
-
|
|
72
|
-
fork processes[name]
|
|
44
|
+
fork procfile[name]
|
|
73
45
|
|
|
74
46
|
trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
|
|
75
47
|
trap("INT") { puts "SIGINT received"; terminate_gracefully }
|
|
@@ -77,9 +49,13 @@ class Foreman::Engine
|
|
|
77
49
|
watch_for_termination
|
|
78
50
|
end
|
|
79
51
|
|
|
52
|
+
def processes
|
|
53
|
+
procfile.processes
|
|
54
|
+
end
|
|
55
|
+
|
|
80
56
|
def port_for(process, num, base_port=nil)
|
|
81
57
|
base_port ||= 5000
|
|
82
|
-
offset =
|
|
58
|
+
offset = procfile.process_names.index(process.name) * 100
|
|
83
59
|
base_port.to_i + offset + num - 1
|
|
84
60
|
end
|
|
85
61
|
|
|
@@ -134,6 +110,24 @@ private ######################################################################
|
|
|
134
110
|
end
|
|
135
111
|
end
|
|
136
112
|
|
|
113
|
+
def terminate_gracefully
|
|
114
|
+
info "sending SIGTERM to all processes"
|
|
115
|
+
kill_all "SIGTERM"
|
|
116
|
+
Timeout.timeout(3) { Process.waitall }
|
|
117
|
+
rescue Timeout::Error
|
|
118
|
+
info "sending SIGKILL to all processes"
|
|
119
|
+
kill_all "SIGKILL"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def watch_for_termination
|
|
123
|
+
pid, status = Process.wait2
|
|
124
|
+
process = running_processes.delete(pid)
|
|
125
|
+
info "process terminated", process
|
|
126
|
+
terminate_gracefully
|
|
127
|
+
kill_all
|
|
128
|
+
rescue Errno::ECHILD
|
|
129
|
+
end
|
|
130
|
+
|
|
137
131
|
def info(message, process=nil)
|
|
138
132
|
print process.color if process
|
|
139
133
|
print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | "
|
|
@@ -149,7 +143,7 @@ private ######################################################################
|
|
|
149
143
|
|
|
150
144
|
def longest_process_name
|
|
151
145
|
@longest_process_name ||= begin
|
|
152
|
-
longest =
|
|
146
|
+
longest = procfile.process_names.map { |name| name.length }.sort.last
|
|
153
147
|
longest = 6 if longest < 6 # system
|
|
154
148
|
longest
|
|
155
149
|
end
|
|
@@ -160,30 +154,10 @@ private ######################################################################
|
|
|
160
154
|
name.ljust(longest_process_name + 3) # add 3 for process number padding
|
|
161
155
|
end
|
|
162
156
|
|
|
163
|
-
def print_info
|
|
164
|
-
info "currently running processes:"
|
|
165
|
-
running_processes.each do |pid, process|
|
|
166
|
-
info "pid #{pid}", process
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
|
|
170
157
|
def proctitle(title)
|
|
171
158
|
$0 = title
|
|
172
159
|
end
|
|
173
160
|
|
|
174
|
-
def read_procfile(procfile)
|
|
175
|
-
File.read(procfile)
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def watch_for_termination
|
|
179
|
-
pid, status = Process.wait2
|
|
180
|
-
process = running_processes.delete(pid)
|
|
181
|
-
info "process terminated", process
|
|
182
|
-
terminate_gracefully
|
|
183
|
-
kill_all
|
|
184
|
-
rescue Errno::ECHILD
|
|
185
|
-
end
|
|
186
|
-
|
|
187
161
|
def running_processes
|
|
188
162
|
@running_processes ||= {}
|
|
189
163
|
end
|
|
@@ -194,41 +168,27 @@ private ######################################################################
|
|
|
194
168
|
@current_color >= COLORS.length ? "" : COLORS[@current_color]
|
|
195
169
|
end
|
|
196
170
|
|
|
197
|
-
def
|
|
198
|
-
return if @already_warned_deprecated
|
|
199
|
-
@already_warned_deprecated = true
|
|
200
|
-
puts "!!! This format of Procfile is deprecated, and will not work starting in v0.12"
|
|
201
|
-
puts "!!! Use a colon to separate the process name from the command"
|
|
202
|
-
puts "!!! e.g. web: thin start"
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
def read_environment(filename)
|
|
206
|
-
error "No such file: #{filename}" if filename && !File.exists?(filename)
|
|
207
|
-
filename ||= ".env"
|
|
171
|
+
def read_environment_files(filenames)
|
|
208
172
|
environment = {}
|
|
209
173
|
|
|
210
|
-
|
|
211
|
-
File.
|
|
212
|
-
|
|
213
|
-
environment[$1] = $2
|
|
214
|
-
end
|
|
215
|
-
end
|
|
174
|
+
(filenames || "").split(",").map(&:strip).each do |filename|
|
|
175
|
+
error "No such file: #{filename}" unless File.exists?(filename)
|
|
176
|
+
environment.merge!(read_environment(filename))
|
|
216
177
|
end
|
|
217
178
|
|
|
179
|
+
environment.merge!(read_environment(".env")) unless filenames
|
|
218
180
|
environment
|
|
219
181
|
end
|
|
220
182
|
|
|
221
|
-
def
|
|
222
|
-
File.
|
|
223
|
-
end
|
|
183
|
+
def read_environment(filename)
|
|
184
|
+
return {} unless File.exists?(filename)
|
|
224
185
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
kill_all "SIGKILL"
|
|
186
|
+
File.read(filename).split("\n").inject({}) do |hash, line|
|
|
187
|
+
if line =~ /\A([A-Za-z_]+)=(.*)\z/
|
|
188
|
+
hash[$1] = $2
|
|
189
|
+
end
|
|
190
|
+
hash
|
|
191
|
+
end
|
|
232
192
|
end
|
|
233
193
|
|
|
234
194
|
end
|
|
@@ -26,7 +26,7 @@ class Foreman::Export::Upstart < Foreman::Export::Base
|
|
|
26
26
|
|
|
27
27
|
process_template = export_template("upstart", "process.conf.erb", template_root)
|
|
28
28
|
|
|
29
|
-
engine.processes.
|
|
29
|
+
engine.processes.each do |process|
|
|
30
30
|
process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
|
|
31
31
|
process_master_config = ERB.new(process_master_template).result(binding)
|
|
32
32
|
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require "foreman"
|
|
2
|
+
|
|
3
|
+
# A valid Procfile entry is captured by this regex.
|
|
4
|
+
# All other lines are ignored.
|
|
5
|
+
#
|
|
6
|
+
# /^([A-Za-z0-9_]+):\s*(.+)$/
|
|
7
|
+
#
|
|
8
|
+
# $1 = name
|
|
9
|
+
# $2 = command
|
|
10
|
+
#
|
|
11
|
+
class Foreman::Procfile
|
|
12
|
+
|
|
13
|
+
attr_reader :processes
|
|
14
|
+
|
|
15
|
+
def initialize(filename)
|
|
16
|
+
@processes = parse_procfile(filename)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def process_names
|
|
20
|
+
processes.map(&:name)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def [](name)
|
|
24
|
+
processes.detect { |process| process.name == name }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def parse_procfile(filename)
|
|
30
|
+
File.read(filename).split("\n").map do |line|
|
|
31
|
+
if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
|
|
32
|
+
Foreman::Process.new($1, $2)
|
|
33
|
+
end
|
|
34
|
+
end.compact
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
data/lib/foreman/version.rb
CHANGED
data/man/foreman.1
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
.\" generated with Ronn/v0.7.3
|
|
2
2
|
.\" http://github.com/rtomayko/ronn/tree/0.7.3
|
|
3
3
|
.
|
|
4
|
-
.TH "FOREMAN" "1" "September 2011" "Foreman 0.
|
|
4
|
+
.TH "FOREMAN" "1" "September 2011" "Foreman 0.23.0" "Foreman Manual"
|
|
5
5
|
.
|
|
6
6
|
.SH "NAME"
|
|
7
7
|
\fBforeman\fR \- manage Procfile\-based applications
|
|
@@ -69,7 +69,7 @@ These options control all modes of foreman\'s operation\.
|
|
|
69
69
|
.
|
|
70
70
|
.TP
|
|
71
71
|
\fB\-e\fR, \fB\-\-env\fR
|
|
72
|
-
Specify an alternate environment file\.
|
|
72
|
+
Specify an alternate environment file\. You can specify more than one file by using: \fB\-\-env file1,file2\fR\.
|
|
73
73
|
.
|
|
74
74
|
.TP
|
|
75
75
|
\fB\-f\fR, \fB\-\-procfile\fR
|
|
@@ -165,7 +165,7 @@ If a \fB\.foreman\fR file exists in the current directory, default options will
|
|
|
165
165
|
.
|
|
166
166
|
.nf
|
|
167
167
|
|
|
168
|
-
concurrency: alpha=0
|
|
168
|
+
concurrency: alpha=0,bravo=1
|
|
169
169
|
port: 15000
|
|
170
170
|
.
|
|
171
171
|
.fi
|
data/spec/foreman/engine_spec.rb
CHANGED
|
@@ -15,21 +15,8 @@ describe "Foreman::Engine" do
|
|
|
15
15
|
before { write_procfile }
|
|
16
16
|
|
|
17
17
|
it "reads the processes" do
|
|
18
|
-
subject.
|
|
19
|
-
subject.
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
describe "with a deprecated Procfile" do
|
|
24
|
-
before do
|
|
25
|
-
File.open("Procfile", "w") do |file|
|
|
26
|
-
file.puts "name command"
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
it "should print a deprecation warning" do
|
|
31
|
-
mock(subject).warn_deprecated_procfile!
|
|
32
|
-
subject.processes.length.should == 1
|
|
18
|
+
subject.procfile["alpha"].command.should == "./alpha"
|
|
19
|
+
subject.procfile["bravo"].command.should == "./bravo"
|
|
33
20
|
end
|
|
34
21
|
end
|
|
35
22
|
end
|
|
@@ -37,8 +24,8 @@ describe "Foreman::Engine" do
|
|
|
37
24
|
describe "start" do
|
|
38
25
|
it "forks the processes" do
|
|
39
26
|
write_procfile
|
|
40
|
-
mock(subject).fork(subject.
|
|
41
|
-
mock(subject).fork(subject.
|
|
27
|
+
mock(subject).fork(subject.procfile["alpha"])
|
|
28
|
+
mock(subject).fork(subject.procfile["bravo"])
|
|
42
29
|
mock(subject).watch_for_termination
|
|
43
30
|
subject.start
|
|
44
31
|
end
|
|
@@ -46,9 +33,9 @@ describe "Foreman::Engine" do
|
|
|
46
33
|
it "handles concurrency" do
|
|
47
34
|
write_procfile
|
|
48
35
|
engine = Foreman::Engine.new("Procfile",:concurrency => "alpha=2")
|
|
49
|
-
mock(engine).fork_individual(engine.
|
|
50
|
-
mock(engine).fork_individual(engine.
|
|
51
|
-
mock(engine).fork_individual(engine.
|
|
36
|
+
mock(engine).fork_individual(engine.procfile["alpha"], 1, 5000)
|
|
37
|
+
mock(engine).fork_individual(engine.procfile["alpha"], 2, 5001)
|
|
38
|
+
mock(engine).fork_individual(engine.procfile["bravo"], 1, 5100)
|
|
52
39
|
mock(engine).watch_for_termination
|
|
53
40
|
engine.start
|
|
54
41
|
end
|
|
@@ -57,14 +44,13 @@ describe "Foreman::Engine" do
|
|
|
57
44
|
describe "execute" do
|
|
58
45
|
it "runs the processes" do
|
|
59
46
|
write_procfile
|
|
60
|
-
mock(subject).fork(subject.
|
|
47
|
+
mock(subject).fork(subject.procfile["alpha"])
|
|
61
48
|
mock(subject).watch_for_termination
|
|
62
49
|
subject.execute("alpha")
|
|
63
50
|
end
|
|
64
51
|
end
|
|
65
52
|
|
|
66
53
|
describe "environment" do
|
|
67
|
-
|
|
68
54
|
before(:each) do
|
|
69
55
|
write_procfile
|
|
70
56
|
stub(Process).fork
|
|
@@ -79,6 +65,16 @@ describe "Foreman::Engine" do
|
|
|
79
65
|
engine.execute("alpha")
|
|
80
66
|
end
|
|
81
67
|
|
|
68
|
+
it "should read more than one if specified" do
|
|
69
|
+
File.open("/tmp/env1", "w") { |f| f.puts("FOO=bar") }
|
|
70
|
+
File.open("/tmp/env2", "w") { |f| f.puts("BAZ=qux") }
|
|
71
|
+
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env1,/tmp/env2")
|
|
72
|
+
stub(engine).info
|
|
73
|
+
mock(engine).watch_for_termination
|
|
74
|
+
engine.environment.should == { "FOO"=>"bar", "BAZ"=>"qux" }
|
|
75
|
+
engine.execute("alpha")
|
|
76
|
+
end
|
|
77
|
+
|
|
82
78
|
it "should fail if specified and doesnt exist" do
|
|
83
79
|
mock.instance_of(Foreman::Engine).error("No such file: /tmp/env")
|
|
84
80
|
engine = Foreman::Engine.new("Procfile", :env => "/tmp/env")
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: foreman
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.24.0
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,11 +9,11 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2011-
|
|
12
|
+
date: 2011-10-04 00:00:00.000000000Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: term-ansicolor
|
|
16
|
-
requirement: &
|
|
16
|
+
requirement: &70155363724260 !ruby/object:Gem::Requirement
|
|
17
17
|
none: false
|
|
18
18
|
requirements:
|
|
19
19
|
- - ~>
|
|
@@ -21,10 +21,10 @@ dependencies:
|
|
|
21
21
|
version: 1.0.5
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
|
-
version_requirements: *
|
|
24
|
+
version_requirements: *70155363724260
|
|
25
25
|
- !ruby/object:Gem::Dependency
|
|
26
26
|
name: thor
|
|
27
|
-
requirement: &
|
|
27
|
+
requirement: &70155363722660 !ruby/object:Gem::Requirement
|
|
28
28
|
none: false
|
|
29
29
|
requirements:
|
|
30
30
|
- - ! '>='
|
|
@@ -32,7 +32,7 @@ dependencies:
|
|
|
32
32
|
version: 0.13.6
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
|
-
version_requirements: *
|
|
35
|
+
version_requirements: *70155363722660
|
|
36
36
|
description: Process manager for applications with multiple components
|
|
37
37
|
email: ddollar@gmail.com
|
|
38
38
|
executables:
|
|
@@ -59,6 +59,7 @@ files:
|
|
|
59
59
|
- lib/foreman/export/upstart.rb
|
|
60
60
|
- lib/foreman/export.rb
|
|
61
61
|
- lib/foreman/process.rb
|
|
62
|
+
- lib/foreman/procfile.rb
|
|
62
63
|
- lib/foreman/utils.rb
|
|
63
64
|
- lib/foreman/version.rb
|
|
64
65
|
- lib/foreman.rb
|
|
@@ -93,7 +94,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
93
94
|
version: '0'
|
|
94
95
|
segments:
|
|
95
96
|
- 0
|
|
96
|
-
hash:
|
|
97
|
+
hash: 3975386254476166563
|
|
97
98
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
98
99
|
none: false
|
|
99
100
|
requirements:
|
|
@@ -102,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
102
103
|
version: '0'
|
|
103
104
|
segments:
|
|
104
105
|
- 0
|
|
105
|
-
hash:
|
|
106
|
+
hash: 3975386254476166563
|
|
106
107
|
requirements: []
|
|
107
108
|
rubyforge_project:
|
|
108
109
|
rubygems_version: 1.8.10
|