foreman 0.15.0 → 0.27.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.
Files changed (49) hide show
  1. data/README.markdown +39 -1
  2. data/data/example/Procfile +2 -2
  3. data/data/example/error +2 -0
  4. data/data/example/ticker +9 -1
  5. data/data/export/bluepill/master.pill.erb +27 -0
  6. data/data/export/runit/log_run.erb +7 -0
  7. data/data/export/runit/run.erb +3 -0
  8. data/data/export/upstart/process.conf.erb +1 -2
  9. data/lib/foreman/cli.rb +12 -9
  10. data/lib/foreman/distribution.rb +9 -0
  11. data/lib/foreman/engine.rb +96 -74
  12. data/lib/foreman/export/base.rb +8 -2
  13. data/lib/foreman/export/bluepill.rb +28 -0
  14. data/lib/foreman/export/inittab.rb +2 -2
  15. data/lib/foreman/export/runit.rb +60 -0
  16. data/lib/foreman/export/upstart.rb +7 -5
  17. data/lib/foreman/export.rb +3 -0
  18. data/lib/foreman/procfile.rb +37 -0
  19. data/lib/foreman/utils.rb +1 -1
  20. data/lib/foreman/version.rb +1 -1
  21. data/lib/foreman.rb +5 -0
  22. data/man/foreman.1 +27 -3
  23. data/spec/foreman/cli_spec.rb +10 -1
  24. data/spec/foreman/engine_spec.rb +59 -24
  25. data/spec/foreman/export/bluepill_spec.rb +20 -0
  26. data/spec/foreman/export/runit_spec.rb +35 -0
  27. data/spec/foreman/export/upstart_spec.rb +43 -9
  28. data/spec/foreman_spec.rb +22 -0
  29. data/spec/resources/export/bluepill/app.pill +65 -0
  30. data/spec/resources/export/runit/app-alpha-1-log-run +7 -0
  31. data/spec/resources/export/runit/app-alpha-1-run +3 -0
  32. data/spec/resources/export/runit/app-alpha-2-log-run +7 -0
  33. data/spec/resources/export/runit/app-alpha-2-run +3 -0
  34. data/spec/resources/export/runit/app-bravo-1-log-run +7 -0
  35. data/spec/resources/export/runit/app-bravo-1-run +3 -0
  36. data/spec/resources/export/upstart/app-alpha-1.conf +5 -0
  37. data/spec/resources/export/upstart/app-alpha-2.conf +5 -0
  38. data/spec/resources/export/upstart/app-alpha.conf +2 -0
  39. data/spec/resources/export/upstart/app-bravo-1.conf +5 -0
  40. data/spec/resources/export/upstart/app-bravo.conf +2 -0
  41. data/spec/resources/export/upstart/app.conf +8 -0
  42. data/spec/spec_helper.rb +16 -5
  43. metadata +60 -124
  44. data/spec/resources/export/upstart/foreman-alpha-1.conf +0 -6
  45. data/spec/resources/export/upstart/foreman-alpha-2.conf +0 -6
  46. data/spec/resources/export/upstart/foreman-alpha.conf +0 -2
  47. data/spec/resources/export/upstart/foreman-bravo-1.conf +0 -6
  48. data/spec/resources/export/upstart/foreman.bravo.conf +0 -2
  49. data/spec/resources/export/upstart/foreman.conf +0 -8
data/README.markdown CHANGED
@@ -1,11 +1,49 @@
1
1
  # Foreman
2
2
 
3
- ## Manual
3
+ ## Installation
4
+
5
+ * Rubygems
6
+
7
+ gem install foreman
8
+
9
+ * OSX
10
+
11
+ http://assets.foreman.io/foreman/foreman.pkg
12
+
13
+ * Standalone Tarball
14
+
15
+ http://assets.foreman.io/foreman/foreman.tgz
16
+
17
+ ## Description
4
18
 
5
19
  http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
6
20
 
21
+ ## Manual
22
+
7
23
  See the [man page](http://ddollar.github.com/foreman) for usage.
8
24
 
25
+ ## Authorship
26
+
27
+ Created by David Dollar
28
+
29
+ Patches contributed by:
30
+
31
+ * Adam Wiggins
32
+ * Dan Peterson
33
+ * Hunter Nield
34
+ * Jay Zeschin
35
+ * Keith Rarick
36
+ * Khaja Minhajuddin
37
+ * Matt Haynes
38
+ * Michael van Rooijen
39
+ * Mike Javorski
40
+ * Nathan L Smith
41
+ * Nick Zadrozny
42
+ * Ricardo Chimal, Jr
43
+ * Thom May
44
+ * clifff
45
+ * Greg Reinacker
46
+
9
47
  ## License
10
48
 
11
49
  MIT
@@ -1,2 +1,2 @@
1
- ticker: ./ticker $PORT
2
- error : ./error
1
+ ticker: ruby ./ticker $PORT
2
+ error: ruby ./error
data/data/example/error CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ $stdout.sync = true
4
+
3
5
  puts "will error in 10s"
4
6
  sleep 5
5
7
  raise "Dying"
data/data/example/ticker CHANGED
@@ -1,6 +1,14 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ $stdout.sync = true
4
+
5
+ %w( SIGINT SIGTERM ).each do |signal|
6
+ trap(signal) do
7
+ puts "received #{signal} but i'm ignoring it!"
8
+ end
9
+ end
10
+
3
11
  while true
4
- puts "tick: #{ARGV.inspect}"
12
+ puts "tick: #{ARGV.inspect} -- FOO:#{ENV["FOO"]}"
5
13
  sleep 1
6
14
  end
@@ -0,0 +1,27 @@
1
+ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
2
+
3
+ app.uid = "<%= user %>"
4
+ app.gid = "<%= user %>"
5
+
6
+ <% engine.processes.each do |process| %>
7
+ <% 1.upto(concurrency[process.name]) do |num| %>
8
+ <% port = engine.port_for(process, num, options[:port]) %>
9
+ app.process("<%= process.name %>-<%=num%>") do |process|
10
+ process.start_command = "<%= process.command.gsub("$PORT", port.to_s) %>"
11
+
12
+ process.working_dir = "<%= engine.directory %>"
13
+ process.daemonize = true
14
+ process.environment = {"PORT" => "<%= port %>"}
15
+ process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
16
+
17
+ process.stdout = process.stderr = "<%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log"
18
+
19
+ process.monitor_children do |children|
20
+ children.stop_command "kill -QUIT {{PID}}"
21
+ end
22
+
23
+ process.group = "<%= app %>-<%= process.name %>"
24
+ end
25
+ <% end %>
26
+ <% end %>
27
+ end
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ LOG=<%= log_root %>/<%= process.name %>-<%= num %>
5
+
6
+ test -d "$LOG" || mkdir -p m2750 "$LOG" && chown <%= user %> "$LOG"
7
+ exec chpst -u <%= user %> svlogd "$LOG"
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+ cd <%= engine.directory %>
3
+ exec chpst -u <%= user %> -e <%= process_env_directory %> <%= process.command %>
@@ -2,5 +2,4 @@ start on starting <%= app %>-<%= process.name %>
2
2
  stop on stopping <%= app %>-<%= process.name %>
3
3
  respawn
4
4
 
5
- chdir <%= engine.directory %>
6
- exec su <%= user %> -c 'export PORT=<%= port %>; <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1'
5
+ exec su - <%= user %> -c 'cd <%= engine.directory %>; export PORT=<%= port %>;<% engine.environment.each_pair do |var,env| %> export <%= var.upcase %>=<%= env %>; <% end %> <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1'
data/lib/foreman/cli.rb CHANGED
@@ -10,17 +10,17 @@ class Foreman::CLI < Thor
10
10
 
11
11
  desc "start [PROCESS]", "Start the application, or a specific process"
12
12
 
13
+ method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
13
14
  method_option :port, :type => :numeric, :aliases => "-p"
14
- method_option :concurrency, :type => :string, :aliases => "-c",
15
- :banner => '"alpha=5,bar=3"'
15
+ method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
16
16
 
17
17
  def start(process=nil)
18
18
  check_procfile!
19
19
 
20
20
  if process
21
- engine.execute(process, options)
21
+ engine.execute(process)
22
22
  else
23
- engine.start(options)
23
+ engine.start
24
24
  end
25
25
  end
26
26
 
@@ -28,8 +28,10 @@ class Foreman::CLI < Thor
28
28
 
29
29
  method_option :app, :type => :string, :aliases => "-a"
30
30
  method_option :log, :type => :string, :aliases => "-l"
31
+ method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
31
32
  method_option :port, :type => :numeric, :aliases => "-p"
32
33
  method_option :user, :type => :string, :aliases => "-u"
34
+ method_option :template, :type => :string, :aliases => "-t"
33
35
  method_option :concurrency, :type => :string, :aliases => "-c",
34
36
  :banner => '"alpha=5,bar=3"'
35
37
 
@@ -39,6 +41,8 @@ class Foreman::CLI < Thor
39
41
  formatter = case format
40
42
  when "inittab" then Foreman::Export::Inittab
41
43
  when "upstart" then Foreman::Export::Upstart
44
+ when "bluepill" then Foreman::Export::Bluepill
45
+ when "runit" then Foreman::Export::Runit
42
46
  else error "Unknown export format: #{format}."
43
47
  end
44
48
 
@@ -51,9 +55,8 @@ class Foreman::CLI < Thor
51
55
  desc "check", "Validate your application's Procfile"
52
56
 
53
57
  def check
54
- processes = engine.processes_in_order.map { |p| p.first }
55
- error "no processes defined" unless processes.length > 0
56
- display "valid procfile detected (#{processes.join(', ')})"
58
+ error "no processes defined" unless engine.processes.length > 0
59
+ display "valid procfile detected (#{engine.processes.map(&:name).join(', ')})"
57
60
  end
58
61
 
59
62
  private ######################################################################
@@ -63,7 +66,7 @@ private ######################################################################
63
66
  end
64
67
 
65
68
  def engine
66
- @engine ||= Foreman::Engine.new(procfile)
69
+ @engine ||= Foreman::Engine.new(procfile, options)
67
70
  end
68
71
 
69
72
  def procfile
@@ -86,7 +89,7 @@ private ######################################################################
86
89
  def options
87
90
  original_options = super
88
91
  return original_options unless File.exists?(".foreman")
89
- defaults = YAML::load_file(".foreman")
92
+ defaults = YAML::load_file(".foreman") || {}
90
93
  Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
91
94
  end
92
95
 
@@ -0,0 +1,9 @@
1
+ module Foreman
2
+ module Distribution
3
+ def self.files
4
+ Dir[File.expand_path("../../../{bin,data,lib}/**/*", __FILE__)].select do |file|
5
+ File.file?(file)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,8 +1,10 @@
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"
7
+ require "timeout"
6
8
  require "term/ansicolor"
7
9
  require "fileutils"
8
10
 
@@ -10,84 +12,73 @@ class Foreman::Engine
10
12
 
11
13
  attr_reader :procfile
12
14
  attr_reader :directory
15
+ attr_reader :options
13
16
 
14
17
  extend Term::ANSIColor
15
18
 
16
19
  COLORS = [ cyan, yellow, green, magenta, red ]
17
20
 
18
- def initialize(procfile)
19
- @procfile = read_procfile(procfile)
21
+ def initialize(procfile, options={})
22
+ @procfile = Foreman::Procfile.new(procfile)
20
23
  @directory = File.expand_path(File.dirname(procfile))
24
+ @options = options
25
+ @environment = read_environment_files(options[:env])
21
26
  end
22
27
 
23
- def processes
24
- @processes ||= begin
25
- @order = []
26
- procfile.split("\n").inject({}) do |hash, line|
27
- next if line.strip == ""
28
- name, command = line.split(/ *: +/, 2)
29
- unless command
30
- warn_deprecated_procfile!
31
- name, command = line.split(/ +/, 2)
32
- end
33
- process = Foreman::Process.new(name, command)
34
- process.color = next_color
35
- @order << process.name
36
- hash.update(process.name => process)
37
- end
38
- end
39
- end
40
-
41
- def process_order
42
- processes
43
- @order.uniq
28
+ def self.load_env!(env_file)
29
+ @environment = read_environment_files(env_file)
30
+ apply_environment!
44
31
  end
45
32
 
46
- def processes_in_order
47
- process_order.map do |name|
48
- [name, processes[name]]
49
- end
50
- end
51
-
52
- def start(options={})
33
+ def start
53
34
  proctitle "ruby: foreman master"
35
+ termtitle "#{File.basename(@directory)} - foreman (#{processes.size} processes)"
54
36
 
55
- processes_in_order.each do |name, process|
56
- fork process, options
37
+ processes.each do |process|
38
+ process.color = next_color
39
+ fork process
57
40
  end
58
41
 
59
- trap("TERM") { puts "SIGTERM received"; kill_all("TERM") }
60
- trap("INT") { puts "SIGINT received"; kill_all("TERM") }
42
+ trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
43
+ trap("INT") { puts "SIGINT received"; terminate_gracefully }
61
44
 
62
45
  watch_for_termination
63
46
  end
64
47
 
65
- def execute(name, options={})
66
- fork processes[name], options
48
+ def execute(name)
49
+ error "no such process: #{name}" unless process = procfile[name]
50
+ process.color = next_color
51
+ fork process
67
52
 
68
- trap("TERM") { puts "SIGTERM received"; kill_all("TERM") }
69
- trap("INT") { puts "SIGINT received"; kill_all("TERM") }
53
+ trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
54
+ trap("INT") { puts "SIGINT received"; terminate_gracefully }
70
55
 
71
56
  watch_for_termination
72
57
  end
73
58
 
59
+ def processes
60
+ procfile.processes
61
+ end
62
+
74
63
  def port_for(process, num, base_port=nil)
75
64
  base_port ||= 5000
76
- offset = processes_in_order.map { |p| p.first }.index(process.name) * 100
65
+ offset = procfile.process_names.index(process.name) * 100
77
66
  base_port.to_i + offset + num - 1
78
67
  end
79
68
 
80
69
  private ######################################################################
81
70
 
82
- def fork(process, options={})
83
- concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
71
+ def fork(process)
72
+ concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
84
73
 
85
74
  1.upto(concurrency[process.name]) do |num|
86
- fork_individual(process, num, port_for(process, num, options[:port]))
75
+ fork_individual(process, num, port_for(process, num, @options[:port]))
87
76
  end
88
77
  end
89
78
 
90
79
  def fork_individual(process, num, port)
80
+ apply_environment!
81
+
91
82
  ENV["PORT"] = port.to_s
92
83
  ENV["PS"] = "#{process.name}.#{num}"
93
84
 
@@ -101,18 +92,18 @@ private ######################################################################
101
92
 
102
93
  def run(process)
103
94
  proctitle "ruby: foreman #{process.name}"
95
+ trap("SIGINT", "IGNORE")
104
96
 
105
97
  begin
106
98
  Dir.chdir directory do
107
- command = process.command
108
-
109
- PTY.spawn("#{process.command} 2>&1") do |stdin, stdout, pid|
99
+ PTY.spawn(process.command) do |stdin, stdout, pid|
100
+ trap("SIGTERM") { Process.kill("SIGTERM", pid) }
110
101
  until stdin.eof?
111
102
  info stdin.gets, process
112
103
  end
113
104
  end
114
105
  end
115
- rescue PTY::ChildExited, Interrupt
106
+ rescue PTY::ChildExited, Interrupt, Errno::EIO, Errno::ENOENT
116
107
  begin
117
108
  info "process exiting", process
118
109
  rescue Interrupt
@@ -120,14 +111,30 @@ private ######################################################################
120
111
  end
121
112
  end
122
113
 
123
- def kill_all(signal="TERM")
124
- info "terminating"
114
+ def kill_all(signal="SIGTERM")
125
115
  running_processes.each do |pid, process|
126
- info "killing #{process.name} in pid #{pid}"
127
- Process.kill(signal, pid)
116
+ Process.kill(signal, pid) rescue Errno::ESRCH
128
117
  end
129
118
  end
130
119
 
120
+ def terminate_gracefully
121
+ info "sending SIGTERM to all processes"
122
+ kill_all "SIGTERM"
123
+ Timeout.timeout(3) { Process.waitall }
124
+ rescue Timeout::Error
125
+ info "sending SIGKILL to all processes"
126
+ kill_all "SIGKILL"
127
+ end
128
+
129
+ def watch_for_termination
130
+ pid, status = Process.wait2
131
+ process = running_processes.delete(pid)
132
+ info "process terminated", process
133
+ terminate_gracefully
134
+ kill_all
135
+ rescue Errno::ECHILD
136
+ end
137
+
131
138
  def info(message, process=nil)
132
139
  print process.color if process
133
140
  print "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(process)} | "
@@ -136,9 +143,14 @@ private ######################################################################
136
143
  puts
137
144
  end
138
145
 
146
+ def error(message)
147
+ puts "ERROR: #{message}"
148
+ exit 1
149
+ end
150
+
139
151
  def longest_process_name
140
152
  @longest_process_name ||= begin
141
- longest = processes.keys.map { |name| name.length }.sort.last
153
+ longest = procfile.process_names.map { |name| name.length }.sort.last
142
154
  longest = 6 if longest < 6 # system
143
155
  longest
144
156
  end
@@ -149,27 +161,12 @@ private ######################################################################
149
161
  name.ljust(longest_process_name + 3) # add 3 for process number padding
150
162
  end
151
163
 
152
- def print_info
153
- info "currently running processes:"
154
- running_processes.each do |pid, process|
155
- info "pid #{pid}", process
156
- end
157
- end
158
-
159
164
  def proctitle(title)
160
165
  $0 = title
161
166
  end
162
167
 
163
- def read_procfile(procfile)
164
- File.read(procfile)
165
- end
166
-
167
- def watch_for_termination
168
- pid, status = Process.wait2
169
- process = running_processes.delete(pid)
170
- info "process terminated", process
171
- kill_all
172
- Process.waitall
168
+ def termtitle(title)
169
+ printf("\033]0;#{title}\007")
173
170
  end
174
171
 
175
172
  def running_processes
@@ -182,12 +179,37 @@ private ######################################################################
182
179
  @current_color >= COLORS.length ? "" : COLORS[@current_color]
183
180
  end
184
181
 
185
- def warn_deprecated_procfile!
186
- return if @already_warned_deprecated
187
- @already_warned_deprecated = true
188
- puts "!!! This format of Procfile is deprecated, and will not work starting in v0.12"
189
- puts "!!! Use a colon to separate the process name from the command"
190
- puts "!!! e.g. web: thin start"
182
+ module Env
183
+ attr_reader :environment
184
+
185
+ def read_environment_files(filenames)
186
+ environment = {}
187
+
188
+ (filenames || "").split(",").map(&:strip).each do |filename|
189
+ error "No such file: #{filename}" unless File.exists?(filename)
190
+ environment.merge!(read_environment(filename))
191
+ end
192
+
193
+ environment.merge!(read_environment(".env")) unless filenames
194
+ environment
195
+ end
196
+
197
+ def read_environment(filename)
198
+ return {} unless File.exists?(filename)
199
+
200
+ File.read(filename).split("\n").inject({}) do |hash, line|
201
+ if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
202
+ hash[$1] = $2
203
+ end
204
+ hash
205
+ end
206
+ end
207
+
208
+ def apply_environment!
209
+ @environment.each { |k,v| ENV[k] = v }
210
+ end
191
211
  end
192
212
 
213
+ include Env
214
+ extend Env
193
215
  end
@@ -23,8 +23,14 @@ private ######################################################################
23
23
  puts "[foreman export] %s" % message
24
24
  end
25
25
 
26
- def export_template(name)
27
- File.read(File.expand_path("../../../../data/export/#{name}", __FILE__))
26
+ def export_template(exporter, file, template_root)
27
+ if template_root && File.exist?(file_path = File.join(template_root, file))
28
+ File.read(file_path)
29
+ elsif File.exist?(file_path = File.join("~/.foreman/templates", file))
30
+ File.read(file_path)
31
+ else
32
+ File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
33
+ end
28
34
  end
29
35
 
30
36
  def write_file(filename, contents)
@@ -0,0 +1,28 @@
1
+ require "erb"
2
+ require "foreman/export"
3
+
4
+ class Foreman::Export::Bluepill < Foreman::Export::Base
5
+
6
+ def export(location, options={})
7
+ error("Must specify a location") unless location
8
+
9
+ FileUtils.mkdir_p location
10
+
11
+ app = options[:app] || File.basename(engine.directory)
12
+ user = options[:user] || app
13
+ log_root = options[:log] || "/var/log/#{app}"
14
+ template_root = options[:template]
15
+
16
+ Dir["#{location}/#{app}.pill"].each do |file|
17
+ say "cleaning up: #{file}"
18
+ FileUtils.rm(file)
19
+ end
20
+
21
+ concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
22
+
23
+ master_template = export_template("bluepill", "master.pill.erb", template_root)
24
+ master_config = ERB.new(master_template).result(binding)
25
+ write_file "#{location}/#{app}.pill", master_config
26
+ end
27
+
28
+ end
@@ -1,4 +1,4 @@
1
- require "foreman/export/base"
1
+ require "foreman/export"
2
2
 
3
3
  class Foreman::Export::Inittab < Foreman::Export::Base
4
4
 
@@ -12,7 +12,7 @@ class Foreman::Export::Inittab < Foreman::Export::Base
12
12
  inittab = []
13
13
  inittab << "# ----- foreman #{app} processes -----"
14
14
 
15
- engine.processes.values.inject(1) do |index, process|
15
+ engine.processes.inject(1) do |index, process|
16
16
  1.upto(concurrency[process.name]) do |num|
17
17
  id = app.slice(0, 2).upcase + sprintf("%02d", index)
18
18
  port = engine.port_for(process, num, options[:port])
@@ -0,0 +1,60 @@
1
+ require "erb"
2
+ require "foreman/export"
3
+
4
+ class Foreman::Export::Runit < Foreman::Export::Base
5
+ ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
6
+
7
+ def export(location, options={})
8
+ error("Must specify a location") unless location
9
+
10
+ app = options[:app] || File.basename(engine.directory)
11
+ user = options[:user] || app
12
+ log_root = options[:log] || "/var/log/#{app}"
13
+ template_root = options[:template]
14
+
15
+ concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
16
+
17
+ run_template = export_template('runit', 'run.erb', template_root)
18
+ log_run_template = export_template('runit', 'log_run.erb', template_root)
19
+
20
+ engine.processes.each do |process|
21
+ 1.upto(concurrency[process.name]) do |num|
22
+ process_directory = "#{location}/#{app}-#{process.name}-#{num}"
23
+ process_env_directory = "#{process_directory}/env"
24
+ process_log_directory = "#{process_directory}/log"
25
+
26
+ create_directory process_directory
27
+ create_directory process_env_directory
28
+ create_directory process_log_directory
29
+
30
+ run = ERB.new(run_template).result(binding)
31
+ write_file "#{process_directory}/run", run
32
+
33
+ port = engine.port_for(process, num, options[:port])
34
+ environment_variables = {'PORT' => port}.
35
+ merge(engine.environment).
36
+ merge(inline_variables(process.command))
37
+
38
+ environment_variables.each_pair do |var, env|
39
+ write_file "#{process_env_directory}/#{var.upcase}", env
40
+ end
41
+
42
+ log_run = ERB.new(log_run_template).result(binding)
43
+ write_file "#{process_log_directory}/run", log_run
44
+
45
+ end
46
+ end
47
+
48
+ end
49
+
50
+ private
51
+ def create_directory(location)
52
+ say "creating: #{location}"
53
+ FileUtils.mkdir(location)
54
+ end
55
+
56
+ def inline_variables(command)
57
+ variable_name_regex =
58
+ Hash[*command.scan(ENV_VARIABLE_REGEX).flatten]
59
+ end
60
+ end
@@ -1,5 +1,5 @@
1
1
  require "erb"
2
- require "foreman/export/base"
2
+ require "foreman/export"
3
3
 
4
4
  class Foreman::Export::Upstart < Foreman::Export::Base
5
5
 
@@ -11,6 +11,7 @@ class Foreman::Export::Upstart < Foreman::Export::Base
11
11
  app = options[:app] || File.basename(engine.directory)
12
12
  user = options[:user] || app
13
13
  log_root = options[:log] || "/var/log/#{app}"
14
+ template_root = options[:template]
14
15
 
15
16
  Dir["#{location}/#{app}*.conf"].each do |file|
16
17
  say "cleaning up: #{file}"
@@ -19,14 +20,15 @@ class Foreman::Export::Upstart < Foreman::Export::Base
19
20
 
20
21
  concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
21
22
 
22
- master_template = export_template("upstart/master.conf.erb")
23
+ master_template = export_template("upstart", "master.conf.erb", template_root)
23
24
  master_config = ERB.new(master_template).result(binding)
24
25
  write_file "#{location}/#{app}.conf", master_config
25
26
 
26
- process_template = export_template("upstart/process.conf.erb")
27
+ process_template = export_template("upstart", "process.conf.erb", template_root)
27
28
 
28
- engine.processes.values.each do |process|
29
- process_master_template = export_template("upstart/process_master.conf.erb")
29
+ engine.processes.each do |process|
30
+ next if (conc = concurrency[process.name]) < 1
31
+ process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
30
32
  process_master_config = ERB.new(process_master_template).result(binding)
31
33
  write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
32
34
 
@@ -4,5 +4,8 @@ module Foreman::Export
4
4
  class Exception < ::Exception; end
5
5
  end
6
6
 
7
+ require "foreman/export/base"
7
8
  require "foreman/export/inittab"
8
9
  require "foreman/export/upstart"
10
+ require "foreman/export/bluepill"
11
+ require "foreman/export/runit"