foreman 0.47.0 → 0.48.0.pre3
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/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 +10 -10
- data/data/export/launchd/launchd.plist.erb +3 -3
- 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/cli.rb +57 -22
- data/lib/foreman/engine/cli.rb +103 -0
- data/lib/foreman/engine.rb +208 -148
- data/lib/foreman/env.rb +27 -0
- data/lib/foreman/export/base.rb +58 -35
- data/lib/foreman/export/bluepill.rb +3 -17
- data/lib/foreman/export/inittab.rb +8 -11
- data/lib/foreman/export/launchd.rb +4 -16
- 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/export.rb +0 -1
- data/lib/foreman/process.rb +56 -67
- data/lib/foreman/procfile.rb +59 -25
- data/lib/foreman/version.rb +1 -1
- data/man/foreman.1 +4 -0
- data/spec/foreman/cli_spec.rb +46 -150
- data/spec/foreman/engine_spec.rb +46 -80
- 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 +4 -7
- 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 +18 -23
- data/spec/foreman/process_spec.rb +27 -124
- 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 +4 -4
- data/spec/resources/export/bluepill/app.pill +4 -4
- 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 +58 -6
- metadata +24 -22
- 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/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/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,219 +9,280 @@ 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] ||= "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 }
|
|
39
42
|
trap("HUP") { puts "SIGHUP received"; terminate_gracefully }
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
startup
|
|
42
45
|
spawn_processes
|
|
43
46
|
watch_for_output
|
|
44
|
-
|
|
47
|
+
sleep 0.1
|
|
48
|
+
watch_for_termination { terminate_gracefully }
|
|
49
|
+
shutdown
|
|
45
50
|
end
|
|
46
51
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
|
51
66
|
end
|
|
52
67
|
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
# Clear the processes registered to this +Engine+
|
|
69
|
+
#
|
|
70
|
+
def clear
|
|
71
|
+
@names = {}
|
|
72
|
+
@processes = []
|
|
55
73
|
end
|
|
56
74
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
when /\A"(.*)"\z/ then hash[key] = $1.gsub(/\\(.)/, '\1')
|
|
66
|
-
else hash[key] = val
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
hash
|
|
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]
|
|
70
83
|
end
|
|
84
|
+
self
|
|
71
85
|
end
|
|
72
86
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
77
96
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
+
@running.each do |pid, (process, index)|
|
|
103
|
+
system "sending #{signal} to #{name_for(pid)} at pid #{pid}"
|
|
104
|
+
begin
|
|
105
|
+
Process.kill(signal, -1 * pid)
|
|
106
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
83
107
|
end
|
|
84
108
|
end
|
|
85
109
|
end
|
|
86
110
|
|
|
87
|
-
|
|
88
|
-
|
|
111
|
+
# Get the process formation
|
|
112
|
+
#
|
|
113
|
+
# @returns [Fixnum] The formation count for the specified process
|
|
114
|
+
#
|
|
115
|
+
def formation
|
|
116
|
+
@formation ||= parse_formation(options[:formation])
|
|
89
117
|
end
|
|
90
118
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
119
|
+
# List the available process names
|
|
120
|
+
#
|
|
121
|
+
# @returns [Array] A list of process names
|
|
122
|
+
#
|
|
123
|
+
def process_names
|
|
124
|
+
@processes.map { |p| @names[p] }
|
|
96
125
|
end
|
|
97
126
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
process = running_processes.delete(pid)
|
|
107
|
-
info "process terminated", process.name
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
rescue Timeout::Error
|
|
111
|
-
info "sending SIGKILL to all processes"
|
|
112
|
-
kill_all "SIGKILL"
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def poll_readers
|
|
116
|
-
rs, ws = IO.select(readers.values, [], [], 1)
|
|
117
|
-
(rs || []).each do |r|
|
|
118
|
-
data = r.gets
|
|
119
|
-
next unless data
|
|
120
|
-
data.force_encoding("BINARY") if data.respond_to?(:force_encoding)
|
|
121
|
-
ps, message = data.split(",", 2)
|
|
122
|
-
color = colors[ps.split(".").first]
|
|
123
|
-
info message, ps, color
|
|
124
|
-
end
|
|
127
|
+
# Get the +Process+ for a specifid name
|
|
128
|
+
#
|
|
129
|
+
# @param [String] name The process name
|
|
130
|
+
#
|
|
131
|
+
# @returns [Foreman::Process] The +Process+ for the specified name
|
|
132
|
+
#
|
|
133
|
+
def process(name)
|
|
134
|
+
@names.invert[name]
|
|
125
135
|
end
|
|
126
136
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
poll_readers
|
|
133
|
-
end
|
|
134
|
-
rescue Exception => ex
|
|
135
|
-
puts ex.message
|
|
136
|
-
puts ex.backtrace
|
|
137
|
-
end
|
|
137
|
+
# Yield each +Process+ in order
|
|
138
|
+
#
|
|
139
|
+
def each_process
|
|
140
|
+
process_names.each do |name|
|
|
141
|
+
yield name, process(name)
|
|
138
142
|
end
|
|
139
143
|
end
|
|
140
144
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
145
|
+
# Get the root directory for this +Engine+
|
|
146
|
+
#
|
|
147
|
+
# @returns [String] The root directory
|
|
148
|
+
#
|
|
149
|
+
def root
|
|
150
|
+
File.expand_path(options[:root] || Dir.pwd)
|
|
147
151
|
end
|
|
148
152
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
# Get the port for a given process and offset
|
|
154
|
+
#
|
|
155
|
+
# @param [Foreman::Process] process A +Process+ associated with this engine
|
|
156
|
+
# @param [Fixnum] instance The instance of the process
|
|
157
|
+
#
|
|
158
|
+
# @returns [Fixnum] port The port to use for this instance of this process
|
|
159
|
+
#
|
|
160
|
+
def port_for(process, instance)
|
|
161
|
+
base_port + (@processes.index(process) * 100) + (instance - 1)
|
|
156
162
|
end
|
|
157
163
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
private
|
|
165
|
+
|
|
166
|
+
### Engine API ######################################################
|
|
167
|
+
|
|
168
|
+
def startup
|
|
169
|
+
raise TypeError, "must use a subclass of Foreman::Engine"
|
|
162
170
|
end
|
|
163
171
|
|
|
164
|
-
def
|
|
165
|
-
|
|
166
|
-
$stdout.puts message
|
|
167
|
-
end
|
|
172
|
+
def output(name, data)
|
|
173
|
+
raise TypeError, "must use a subclass of Foreman::Engine"
|
|
168
174
|
end
|
|
169
175
|
|
|
170
|
-
def
|
|
171
|
-
|
|
172
|
-
longest = procfile.process_names.map { |name| name.length }.sort.last
|
|
173
|
-
longest = 6 if longest < 6 # system
|
|
174
|
-
longest
|
|
175
|
-
end
|
|
176
|
+
def shutdown
|
|
177
|
+
raise TypeError, "must use a subclass of Foreman::Engine"
|
|
176
178
|
end
|
|
177
179
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
+
## Helpers ##########################################################
|
|
181
|
+
|
|
182
|
+
def base_port
|
|
183
|
+
(options[:port] || env["PORT"] || ENV["PORT"] || 5000).to_i
|
|
180
184
|
end
|
|
181
185
|
|
|
182
|
-
def
|
|
183
|
-
|
|
186
|
+
def create_pipe
|
|
187
|
+
IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
|
|
184
188
|
end
|
|
185
189
|
|
|
186
|
-
def
|
|
187
|
-
|
|
190
|
+
def name_for(pid)
|
|
191
|
+
process, index = @running[pid]
|
|
192
|
+
[ @names[process], index.to_s ].compact.join(".")
|
|
188
193
|
end
|
|
189
194
|
|
|
190
|
-
def
|
|
191
|
-
@
|
|
195
|
+
def parse_formation(formation)
|
|
196
|
+
pairs = @options[:formation].to_s.gsub(/\s/, "").split(",")
|
|
197
|
+
|
|
198
|
+
pairs.inject(Hash.new(0)) do |ax, pair|
|
|
199
|
+
process, amount = pair.split("=")
|
|
200
|
+
process == "all" ? ax.default = amount.to_i : ax[process] = amount.to_i
|
|
201
|
+
ax
|
|
202
|
+
end
|
|
192
203
|
end
|
|
193
204
|
|
|
194
|
-
def
|
|
195
|
-
@
|
|
205
|
+
def output_with_mutex(name, message)
|
|
206
|
+
@mutex.synchronize do
|
|
207
|
+
output name, message
|
|
208
|
+
end
|
|
196
209
|
end
|
|
197
210
|
|
|
198
|
-
def
|
|
199
|
-
|
|
211
|
+
def system(message)
|
|
212
|
+
output_with_mutex "system", message
|
|
200
213
|
end
|
|
201
214
|
|
|
202
|
-
def
|
|
203
|
-
|
|
204
|
-
|
|
215
|
+
def termination_message_for(status)
|
|
216
|
+
if status.exited?
|
|
217
|
+
"exited with code #{status.exitstatus}"
|
|
218
|
+
elsif status.signaled?
|
|
219
|
+
"terminated by SIG#{Signal.list.invert[status.termsig]}"
|
|
220
|
+
else
|
|
221
|
+
"died a mysterious death"
|
|
205
222
|
end
|
|
206
223
|
end
|
|
207
224
|
|
|
208
|
-
def
|
|
209
|
-
|
|
225
|
+
def flush_reader(reader)
|
|
226
|
+
until reader.eof?
|
|
227
|
+
data = reader.gets
|
|
228
|
+
output_with_mutex name_for(@readers.key(reader)), data
|
|
229
|
+
end
|
|
210
230
|
end
|
|
211
231
|
|
|
212
|
-
|
|
213
|
-
environment = {}
|
|
232
|
+
## Engine ###########################################################
|
|
214
233
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
234
|
+
def spawn_processes
|
|
235
|
+
@processes.each do |process|
|
|
236
|
+
1.upto(formation[@names[process]]) do |n|
|
|
237
|
+
reader, writer = create_pipe
|
|
238
|
+
begin
|
|
239
|
+
pid = process.run(:output => writer, :env => { "PORT" => port_for(process, n).to_s })
|
|
240
|
+
writer.puts "started with pid #{pid}"
|
|
241
|
+
rescue Errno::ENOENT
|
|
242
|
+
writer.puts "unknown command: #{process.command}"
|
|
243
|
+
end
|
|
244
|
+
@running[pid] = [process, n]
|
|
245
|
+
@readers[pid] = reader
|
|
246
|
+
end
|
|
218
247
|
end
|
|
248
|
+
end
|
|
219
249
|
|
|
220
|
-
|
|
250
|
+
def watch_for_output
|
|
251
|
+
Thread.new do
|
|
252
|
+
begin
|
|
253
|
+
loop do
|
|
254
|
+
(IO.select(@readers.values).first || []).each do |reader|
|
|
255
|
+
data = reader.gets
|
|
256
|
+
output_with_mutex name_for(@readers.key(reader)), data
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
rescue Exception => ex
|
|
260
|
+
puts ex.message
|
|
261
|
+
puts ex.backtrace
|
|
262
|
+
end
|
|
263
|
+
end
|
|
221
264
|
end
|
|
222
265
|
|
|
223
|
-
def
|
|
224
|
-
|
|
225
|
-
|
|
266
|
+
def watch_for_termination
|
|
267
|
+
pid, status = Process.wait2
|
|
268
|
+
output_with_mutex name_for(pid), termination_message_for(status)
|
|
269
|
+
@running.delete(pid)
|
|
270
|
+
yield if block_given?
|
|
271
|
+
pid
|
|
272
|
+
rescue Errno::ECHILD
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def terminate_gracefully
|
|
276
|
+
return if @terminating
|
|
277
|
+
@terminating = true
|
|
278
|
+
system "sending SIGTERM to all processes"
|
|
279
|
+
killall "SIGTERM"
|
|
280
|
+
Timeout.timeout(5) do
|
|
281
|
+
watch_for_termination while @running.length > 0
|
|
282
|
+
end
|
|
283
|
+
rescue Timeout::Error
|
|
284
|
+
system "sending SIGKILL to all processes"
|
|
285
|
+
killall "SIGKILL"
|
|
226
286
|
end
|
|
227
287
|
|
|
228
288
|
end
|
data/lib/foreman/env.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "foreman"
|
|
2
|
+
|
|
3
|
+
class Foreman::Env
|
|
4
|
+
|
|
5
|
+
attr_reader :entries
|
|
6
|
+
|
|
7
|
+
def initialize(filename)
|
|
8
|
+
@entries = File.read(filename).split("\n").inject({}) do |ax, line|
|
|
9
|
+
if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
|
|
10
|
+
key = $1
|
|
11
|
+
case val = $2
|
|
12
|
+
when /\A'(.*)'\z/ then ax[key] = $1
|
|
13
|
+
when /\A"(.*)"\z/ then ax[key] = $1.gsub(/\\(.)/, '\1')
|
|
14
|
+
else ax[key] = val
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
ax
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def entries
|
|
22
|
+
@entries.each do |key, value|
|
|
23
|
+
yield key, value
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
data/lib/foreman/export/base.rb
CHANGED
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
require "foreman/export"
|
|
2
|
-
require "
|
|
2
|
+
require "shellwords"
|
|
3
3
|
|
|
4
4
|
class Foreman::Export::Base
|
|
5
5
|
|
|
6
|
-
attr_reader :location
|
|
6
|
+
attr_reader :location
|
|
7
|
+
attr_reader :engine
|
|
8
|
+
attr_reader :options
|
|
9
|
+
attr_reader :formation
|
|
7
10
|
|
|
8
11
|
def initialize(location, engine, options={})
|
|
9
|
-
@location
|
|
10
|
-
@engine
|
|
11
|
-
@
|
|
12
|
-
@
|
|
13
|
-
@port = options[:port]
|
|
14
|
-
@user = options[:user]
|
|
15
|
-
@template = options[:template]
|
|
16
|
-
@concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
|
12
|
+
@location = location
|
|
13
|
+
@engine = engine
|
|
14
|
+
@options = options.dup
|
|
15
|
+
@formation = engine.formation
|
|
17
16
|
end
|
|
18
17
|
|
|
19
18
|
def export
|
|
20
|
-
|
|
19
|
+
error("Must specify a location") unless location
|
|
20
|
+
FileUtils.mkdir_p(location) rescue error("Could not create: #{location}")
|
|
21
|
+
FileUtils.mkdir_p(log) rescue error("Could not create: #{log}")
|
|
22
|
+
FileUtils.chown(user, nil, log) rescue error("Could not chown #{log} to #{user}")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def app
|
|
26
|
+
options[:app] || "app"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def log
|
|
30
|
+
options[:log] || "/var/log/#{app}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def user
|
|
34
|
+
options[:user] || app
|
|
21
35
|
end
|
|
22
36
|
|
|
23
37
|
private ######################################################################
|
|
@@ -29,38 +43,47 @@ private ######################################################################
|
|
|
29
43
|
def say(message)
|
|
30
44
|
puts "[foreman export] %s" % message
|
|
31
45
|
end
|
|
46
|
+
|
|
47
|
+
def clean(filename)
|
|
48
|
+
return unless File.exists?(filename)
|
|
49
|
+
say "cleaning up: #{filename}"
|
|
50
|
+
FileUtils.rm(filename)
|
|
51
|
+
end
|
|
32
52
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
53
|
+
def shell_quote(value)
|
|
54
|
+
'"' + Shellwords.escape(value) + '"'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def export_template(name)
|
|
58
|
+
name_without_first = name.split("/")[1..-1].join("/")
|
|
59
|
+
matchers = []
|
|
60
|
+
matchers << File.join(options[:template], name_without_first) if options[:template]
|
|
61
|
+
matchers << File.expand_path("~/.foreman/templates/#{name}")
|
|
62
|
+
matchers << File.expand_path("../../../../data/export/#{name}", __FILE__)
|
|
63
|
+
File.read(matchers.detect { |m| File.exists?(m) })
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def write_template(name, target, binding)
|
|
67
|
+
compiled = ERB.new(export_template(name)).result(binding)
|
|
68
|
+
write_file target, compiled
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def chmod(mode, file)
|
|
72
|
+
say "setting #{file} to mode #{mode}"
|
|
73
|
+
FileUtils.chmod mode, File.join(location, file)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def create_directory(dir)
|
|
77
|
+
say "creating: #{dir}"
|
|
78
|
+
FileUtils.mkdir_p(File.join(location, dir))
|
|
41
79
|
end
|
|
42
80
|
|
|
43
81
|
def write_file(filename, contents)
|
|
44
82
|
say "writing: #{filename}"
|
|
45
83
|
|
|
46
|
-
File.open(filename, "w") do |file|
|
|
84
|
+
File.open(File.join(location, filename), "w") do |file|
|
|
47
85
|
file.puts contents
|
|
48
86
|
end
|
|
49
87
|
end
|
|
50
88
|
|
|
51
|
-
# Quote a string to be used on the command line. Backslashes are escapde to \\ and quotes
|
|
52
|
-
# escaped to \"
|
|
53
|
-
#
|
|
54
|
-
# str - string to be quoted
|
|
55
|
-
#
|
|
56
|
-
# Examples
|
|
57
|
-
#
|
|
58
|
-
# shell_quote("FB|123\"\\1")
|
|
59
|
-
# # => "\"FB|123\"\\"\\\\1\""
|
|
60
|
-
#
|
|
61
|
-
# Returns the the escaped string surrounded by quotes
|
|
62
|
-
def shell_quote(str)
|
|
63
|
-
"\"#{str.gsub(/\\/){ '\\\\' }.gsub(/["]/){ "\\\"" }}\""
|
|
64
|
-
end
|
|
65
|
-
|
|
66
89
|
end
|
|
@@ -4,23 +4,9 @@ require "foreman/export"
|
|
|
4
4
|
class Foreman::Export::Bluepill < Foreman::Export::Base
|
|
5
5
|
|
|
6
6
|
def export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
16
|
-
Dir["#{location}/#{app}.pill"].each do |file|
|
|
17
|
-
say "cleaning up: #{file}"
|
|
18
|
-
FileUtils.rm(file)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
master_template = export_template("bluepill", "master.pill.erb", template_root)
|
|
22
|
-
master_config = ERB.new(master_template).result(binding)
|
|
23
|
-
write_file "#{location}/#{app}.pill", master_config
|
|
7
|
+
super
|
|
8
|
+
clean "#{location}/#{app}.pill"
|
|
9
|
+
write_template "bluepill/master.pill.erb", "#{app}.pill", binding
|
|
24
10
|
end
|
|
25
11
|
|
|
26
12
|
end
|
|
@@ -3,21 +3,19 @@ require "foreman/export"
|
|
|
3
3
|
class Foreman::Export::Inittab < Foreman::Export::Base
|
|
4
4
|
|
|
5
5
|
def export
|
|
6
|
-
|
|
7
|
-
user = self.user || app
|
|
8
|
-
log_root = self.log || "/var/log/#{app}"
|
|
6
|
+
error("Must specify a location") unless location
|
|
9
7
|
|
|
10
8
|
inittab = []
|
|
11
9
|
inittab << "# ----- foreman #{app} processes -----"
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
index = 1
|
|
12
|
+
engine.each_process do |name, process|
|
|
13
|
+
1.upto(engine.formation[name]) do |num|
|
|
15
14
|
id = app.slice(0, 2).upcase + sprintf("%02d", index)
|
|
16
|
-
port = engine.port_for(process, num
|
|
17
|
-
inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{
|
|
15
|
+
port = engine.port_for(process, num)
|
|
16
|
+
inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log}/#{name}-#{num}.log 2>&1'"
|
|
18
17
|
index += 1
|
|
19
18
|
end
|
|
20
|
-
index
|
|
21
19
|
end
|
|
22
20
|
|
|
23
21
|
inittab << "# ----- end foreman #{app} processes -----"
|
|
@@ -27,9 +25,8 @@ class Foreman::Export::Inittab < Foreman::Export::Base
|
|
|
27
25
|
if location == "-"
|
|
28
26
|
puts inittab
|
|
29
27
|
else
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
write_file(location, inittab)
|
|
28
|
+
say "writing: #{location}"
|
|
29
|
+
File.open(location, "w") { |file| file.puts inittab }
|
|
33
30
|
end
|
|
34
31
|
end
|
|
35
32
|
|