foreman 0.46.0 → 0.48.0.pre1

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.
Files changed (67) hide show
  1. data/README.md +6 -0
  2. data/bin/foreman-runner +3 -7
  3. data/bin/taskman +8 -0
  4. data/data/example/Procfile +4 -3
  5. data/data/example/spawnee +14 -0
  6. data/data/example/spawner +7 -0
  7. data/data/export/bluepill/master.pill.erb +11 -10
  8. data/data/export/launchd/launchd.plist.erb +22 -0
  9. data/data/export/runit/log/run.erb +7 -0
  10. data/data/export/runit/run.erb +2 -2
  11. data/data/export/supervisord/app.conf.erb +12 -12
  12. data/data/export/upstart/master.conf.erb +2 -2
  13. data/data/export/upstart/process.conf.erb +3 -3
  14. data/lib/foreman/cli.rb +51 -22
  15. data/lib/foreman/engine/cli.rb +98 -0
  16. data/lib/foreman/engine.rb +209 -148
  17. data/lib/foreman/env.rb +27 -0
  18. data/lib/foreman/export/base.rb +58 -20
  19. data/lib/foreman/export/bluepill.rb +3 -17
  20. data/lib/foreman/export/inittab.rb +8 -11
  21. data/lib/foreman/export/launchd.rb +15 -0
  22. data/lib/foreman/export/runit.rb +14 -39
  23. data/lib/foreman/export/supervisord.rb +3 -13
  24. data/lib/foreman/export/upstart.rb +9 -27
  25. data/lib/foreman/export.rb +1 -1
  26. data/lib/foreman/process.rb +56 -67
  27. data/lib/foreman/procfile.rb +59 -25
  28. data/lib/foreman/version.rb +1 -1
  29. data/man/foreman.1 +5 -1
  30. data/spec/foreman/cli_spec.rb +38 -152
  31. data/spec/foreman/engine_spec.rb +47 -74
  32. data/spec/foreman/export/base_spec.rb +4 -7
  33. data/spec/foreman/export/bluepill_spec.rb +7 -6
  34. data/spec/foreman/export/inittab_spec.rb +7 -7
  35. data/spec/foreman/export/launchd_spec.rb +21 -0
  36. data/spec/foreman/export/runit_spec.rb +12 -17
  37. data/spec/foreman/export/supervisord_spec.rb +7 -56
  38. data/spec/foreman/export/upstart_spec.rb +22 -21
  39. data/spec/foreman/process_spec.rb +27 -110
  40. data/spec/foreman/procfile_spec.rb +26 -16
  41. data/spec/resources/Procfile +4 -0
  42. data/spec/resources/bin/echo +2 -0
  43. data/spec/resources/bin/env +2 -0
  44. data/spec/resources/bin/test +2 -0
  45. data/spec/resources/export/bluepill/app-concurrency.pill +6 -4
  46. data/spec/resources/export/bluepill/app.pill +6 -4
  47. data/spec/resources/export/launchd/launchd-a.default +22 -0
  48. data/spec/resources/export/launchd/launchd-b.default +22 -0
  49. data/spec/resources/export/supervisord/app-alpha-1.conf +24 -0
  50. data/spec/resources/export/supervisord/app-alpha-2.conf +4 -4
  51. data/spec/spec_helper.rb +58 -6
  52. metadata +29 -28
  53. data/data/export/runit/log_run.erb +0 -7
  54. data/lib/foreman/color.rb +0 -40
  55. data/lib/foreman/procfile_entry.rb +0 -26
  56. data/lib/foreman/utils.rb +0 -18
  57. data/spec/foreman/color_spec.rb +0 -31
  58. data/spec/foreman/procfile_entry_spec.rb +0 -13
  59. data/spec/resources/export/supervisord/app-env-with-comma.conf +0 -24
  60. data/spec/resources/export/supervisord/app-env.conf +0 -21
  61. data/spec/resources/export/supervisord/app.conf +0 -24
  62. /data/spec/resources/export/runit/{app-alpha-1-log-run → app-alpha-1/log/run} +0 -0
  63. /data/spec/resources/export/runit/{app-alpha-1-run → app-alpha-1/run} +0 -0
  64. /data/spec/resources/export/runit/{app-alpha-2-log-run → app-alpha-2/log/run} +0 -0
  65. /data/spec/resources/export/runit/{app-alpha-2-run → app-alpha-2/run} +0 -0
  66. /data/spec/resources/export/runit/{app-bravo-1-log-run → app-bravo-1/log/run} +0 -0
  67. /data/spec/resources/export/runit/{app-bravo-1-run → app-bravo-1/run} +0 -0
@@ -1,8 +1,7 @@
1
1
  require "foreman"
2
- require "foreman/color"
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,280 @@ require "thread"
10
9
 
11
10
  class Foreman::Engine
12
11
 
13
- attr_reader :environment
14
- attr_reader :procfile
15
- attr_reader :directory
12
+ attr_reader :env
16
13
  attr_reader :options
17
-
18
- COLORS = %w( cyan yellow green magenta red blue intense_cyan intense_yellow
19
- intense_green intense_magenta intense_red, intense_blue )
20
-
21
- Foreman::Color.enable($stdout)
22
-
23
- def initialize(procfile, options={})
24
- @procfile = Foreman::Procfile.new(procfile) if File.exists?(procfile)
25
- @directory = options[:app_root] || File.expand_path(File.dirname(procfile))
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[:env] ||= default_env
30
- @environment = read_environment_files(@options[:env])
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 }
42
+ trap("HUP") { puts "SIGHUP received"; terminate_gracefully }
39
43
 
40
- assign_colors
44
+ startup
41
45
  spawn_processes
42
46
  watch_for_output
43
- watch_for_termination
47
+ sleep 0.1
48
+ watch_for_termination { terminate_gracefully }
49
+ shutdown
44
50
  end
45
51
 
46
- def port_for(process, num, base_port=nil)
47
- base_port ||= 5000
48
- offset = procfile.process_names.index(process.name) * 100
49
- base_port.to_i + offset + num - 1
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
- def apply_environment!
53
- environment.each { |k,v| ENV[k] = v }
68
+ # Clear the processes registered to this +Engine+
69
+ #
70
+ def clear
71
+ @names = {}
72
+ @processes = []
54
73
  end
55
74
 
56
- def self.read_environment(filename)
57
- return {} unless File.exists?(filename)
58
-
59
- File.read(filename).split("\n").inject({}) do |hash, line|
60
- if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
61
- key, val = [$1, $2]
62
- case val
63
- when /\A'(.*)'\z/ then hash[key] = $1
64
- when /\A"(.*)"\z/ then hash[key] = $1.gsub(/\\(.)/, '\1')
65
- else hash[key] = val
66
- end
67
- end
68
- 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]
69
83
  end
84
+ self
70
85
  end
71
86
 
72
- private ######################################################################
73
-
74
- def spawn_processes
75
- concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
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
76
96
 
77
- procfile.entries.each do |entry|
78
- reader, writer = (IO.method(:pipe).arity == 0 ? IO.pipe : IO.pipe("BINARY"))
79
- entry.spawn(concurrency[entry.name], writer, @directory, @environment, port_for(entry, 1, base_port)).each do |process|
80
- running_processes[process.pid] = process
81
- readers[process] = reader
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
82
107
  end
83
108
  end
84
109
  end
85
110
 
86
- def base_port
87
- options[:port] || 5000
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])
88
117
  end
89
118
 
90
- def kill_all(signal="SIGTERM")
91
- running_processes.each do |pid, process|
92
- info "sending #{signal} to pid #{pid}"
93
- process.kill signal
94
- end
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] }
95
125
  end
96
126
 
97
- def terminate_gracefully
98
- return if @terminating
99
- @terminating = true
100
- info "sending SIGTERM to all processes"
101
- kill_all "SIGTERM"
102
- Timeout.timeout(5) do
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
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]
124
135
  end
125
136
 
126
- def watch_for_output
127
- Thread.new do
128
- require "win32console" if Foreman.windows?
129
- begin
130
- loop do
131
- poll_readers
132
- end
133
- rescue Exception => ex
134
- puts ex.message
135
- puts ex.backtrace
136
- end
137
+ # Yield each +Process+ in order
138
+ #
139
+ def each_process
140
+ process_names.each do |name|
141
+ yield name, process(name)
137
142
  end
138
143
  end
139
144
 
140
- def watch_for_termination
141
- pid, status = Process.wait2
142
- process = running_processes.delete(pid)
143
- info "process terminated", process.name
144
- terminate_gracefully
145
- rescue Errno::ECHILD
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)
146
151
  end
147
152
 
148
- def info(message, name="system", color=:white)
149
- output = ""
150
- output += $stdout.color(color)
151
- output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
152
- output += $stdout.color(:reset)
153
- output += message.chomp
154
- puts output
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)
155
162
  end
156
163
 
157
- def print(message=nil)
158
- @output_mutex.synchronize do
159
- $stdout.print message
160
- end
164
+ private
165
+
166
+ ### Engine API ######################################################
167
+
168
+ def startup
169
+ raise TypeError, "must use a subclass of Foreman::Engine"
161
170
  end
162
171
 
163
- def puts(message=nil)
164
- @output_mutex.synchronize do
165
- $stdout.puts message
166
- end
172
+ def output(name, data)
173
+ raise TypeError, "must use a subclass of Foreman::Engine"
167
174
  end
168
175
 
169
- def longest_process_name
170
- @longest_process_name ||= begin
171
- longest = procfile.process_names.map { |name| name.length }.sort.last
172
- longest = 6 if longest < 6 # system
173
- longest
174
- end
176
+ def shutdown
177
+ raise TypeError, "must use a subclass of Foreman::Engine"
175
178
  end
176
179
 
177
- def pad_process_name(name="system")
178
- name.to_s.ljust(longest_process_name + 3) # add 3 for process number padding
180
+ ## Helpers ##########################################################
181
+
182
+ def base_port
183
+ (options[:port] || env["PORT"] || ENV["PORT"] || 5000).to_i
179
184
  end
180
185
 
181
- def proctitle(title)
182
- $0 = title
186
+ def create_pipe
187
+ IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
183
188
  end
184
189
 
185
- def termtitle(title)
186
- printf("\033]0;#{title}\007") unless Foreman.windows?
190
+ def name_for(pid)
191
+ process, index = @running[pid]
192
+ [ @names[process], index.to_s ].compact.join(".")
187
193
  end
188
194
 
189
- def running_processes
190
- @running_processes ||= {}
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
191
203
  end
192
204
 
193
- def readers
194
- @readers ||= {}
205
+ def output_with_mutex(name, message)
206
+ @mutex.synchronize do
207
+ output name, message
208
+ end
195
209
  end
196
210
 
197
- def colors
198
- @colors ||= {}
211
+ def system(message)
212
+ output_with_mutex "system", message
199
213
  end
200
214
 
201
- def assign_colors
202
- procfile.entries.each_with_index do |entry, idx|
203
- colors[entry.name] = COLORS[idx % COLORS.length]
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"
204
222
  end
205
223
  end
206
224
 
207
- def process_by_reader(reader)
208
- readers.invert[reader]
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
209
230
  end
210
231
 
211
- def read_environment_files(filenames)
212
- environment = {}
232
+ ## Engine ###########################################################
213
233
 
214
- (filenames || "").split(",").map(&:strip).each do |filename|
215
- error "No such file: #{filename}" unless File.exists?(filename)
216
- environment.merge!(Foreman::Engine.read_environment(filename))
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
217
247
  end
248
+ end
218
249
 
219
- environment
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
220
264
  end
221
265
 
222
- def default_env
223
- env = File.join(directory, ".env")
224
- File.exists?(env) ? env : ""
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"
225
286
  end
226
287
 
227
288
  end
@@ -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
@@ -1,23 +1,37 @@
1
1
  require "foreman/export"
2
- require "foreman/utils"
2
+ require "shellwords"
3
3
 
4
4
  class Foreman::Export::Base
5
5
 
6
- attr_reader :location, :engine, :app, :log, :port, :user, :template, :concurrency
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 = 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])
12
+ @location = location
13
+ @engine = engine
14
+ @options = options.dup
15
+ @formation = engine.formation
17
16
  end
18
17
 
19
18
  def export
20
- raise "export method must be overridden"
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,21 +43,45 @@ 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 export_template(exporter, file, template_root)
34
- if template_root && File.exist?(file_path = File.join(template_root, file))
35
- File.read(file_path)
36
- elsif File.exist?(file_path = File.expand_path(File.join("~/.foreman/templates", file)))
37
- File.read(file_path)
38
- else
39
- File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
40
- end
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
@@ -4,23 +4,9 @@ require "foreman/export"
4
4
  class Foreman::Export::Bluepill < Foreman::Export::Base
5
5
 
6
6
  def export
7
- error("Must specify a location") unless location
8
-
9
- FileUtils.mkdir_p location
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
- app = self.app || File.basename(engine.directory)
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
- engine.procfile.entries.inject(1) do |index, process|
14
- 1.upto(self.concurrency[process.name]) do |num|
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, self.port)
17
- inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log_root}/#{process.name}-#{num}.log 2>&1'"
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
- FileUtils.mkdir_p(log_root) rescue error "could not create #{log_root}"
31
- FileUtils.chown(user, nil, log_root) rescue error "could not chown #{log_root} to #{user}"
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
 
@@ -0,0 +1,15 @@
1
+ require "erb"
2
+ require "foreman/export"
3
+
4
+ class Foreman::Export::Launchd < Foreman::Export::Base
5
+
6
+ def export
7
+ super
8
+ engine.each_process do |name, process|
9
+ 1.upto(engine.formation[name]) do |num|
10
+ write_template "launchd/launchd.plist.erb", "#{app}-#{name}-#{num}.plist", binding
11
+ end
12
+ end
13
+ end
14
+
15
+ end