foreman 0.29.0 → 0.37.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 +39 -0
- data/bin/runner +35 -1
- data/data/export/bluepill/master.pill.erb +1 -1
- data/lib/foreman/cli.rb +30 -23
- data/lib/foreman/engine.rb +35 -20
- data/lib/foreman/export/base.rb +12 -5
- data/lib/foreman/export/bluepill.rb +5 -7
- data/lib/foreman/export/inittab.rb +10 -12
- data/lib/foreman/export/runit.rb +10 -11
- data/lib/foreman/export/upstart.rb +8 -10
- data/lib/foreman/export.rb +21 -0
- data/lib/foreman/helpers.rb +45 -0
- data/lib/foreman/process.rb +52 -15
- data/lib/foreman/utils.rb +4 -1
- data/lib/foreman/version.rb +1 -1
- data/lib/foreman.rb +8 -1
- data/man/foreman.1 +5 -1
- data/spec/foreman/cli_spec.rb +77 -7
- data/spec/foreman/engine_spec.rb +5 -5
- data/spec/foreman/export/base_spec.rb +22 -0
- data/spec/foreman/export/bluepill_spec.rb +22 -5
- 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 +8 -7
- data/spec/helper_spec.rb +18 -0
- data/spec/resources/export/bluepill/app-concurrency.pill +47 -0
- data/spec/resources/export/bluepill/app.pill +0 -21
- data/spec/resources/export/inittab/inittab.concurrency +4 -0
- data/spec/resources/export/inittab/inittab.default +4 -0
- data/spec/spec_helper.rb +31 -3
- metadata +17 -15
- data/README.markdown +0 -49
data/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
|
|
29
|
+
## Authors
|
|
30
|
+
|
|
31
|
+
#### Created and maintained by
|
|
32
|
+
David Dollar
|
|
33
|
+
|
|
34
|
+
#### Patches contributed by
|
|
35
|
+
Adam Wiggins, Chris Continanza, Chris Lowder, Craig R Webster, Dan Farina, Dan Peterson, David Dollar, Fletcher Nichol, Gabriel Burt, Gamaliel Toro, Greg Reinacker, Hugues Le Gendre, Hunter Nield, Iain Hecker, Jay Zeschin, Keith Rarick, Khaja Minhajuddin, Lincoln Stoll, Marcos Muino Garcia, Mark McGranaghan, Matt Griffin, Matt Haynes, Matthijs Langenberg, Michael Dwan, Michael van Rooijen, Mike Javorski, Nathan Broadbent, Nathan L Smith, Nick Zadrozny, Phil Hagelberg, Ricardo Chimal, Jr, Thom May, Tom Ward, brainopia, clifff, jc00ke
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
MIT
|
data/bin/runner
CHANGED
|
@@ -1,2 +1,36 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
|
|
2
|
+
#
|
|
3
|
+
#/ Usage: 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
|
|
@@ -5,7 +5,7 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
|
|
|
5
5
|
|
|
6
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
|
|
data/lib/foreman/cli.rb
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
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
13
|
desc "start", "Start the application"
|
|
12
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"
|
|
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
|
|
18
31
|
check_procfile!
|
|
19
32
|
engine.start
|
|
@@ -27,22 +40,12 @@ class Foreman::CLI < Thor
|
|
|
27
40
|
method_option :port, :type => :numeric, :aliases => "-p"
|
|
28
41
|
method_option :user, :type => :string, :aliases => "-u"
|
|
29
42
|
method_option :template, :type => :string, :aliases => "-t"
|
|
30
|
-
method_option :concurrency, :type => :string, :aliases => "-c",
|
|
31
|
-
:banner => '"alpha=5,bar=3"'
|
|
43
|
+
method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
|
|
32
44
|
|
|
33
45
|
def export(format, location=nil)
|
|
34
46
|
check_procfile!
|
|
35
|
-
|
|
36
|
-
formatter
|
|
37
|
-
when "inittab" then Foreman::Export::Inittab
|
|
38
|
-
when "upstart" then Foreman::Export::Upstart
|
|
39
|
-
when "bluepill" then Foreman::Export::Bluepill
|
|
40
|
-
when "runit" then Foreman::Export::Runit
|
|
41
|
-
else error "Unknown export format: #{format}."
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
formatter.new(engine).export(location, options)
|
|
45
|
-
|
|
47
|
+
formatter = Foreman::Export.formatter(format)
|
|
48
|
+
formatter.new(location, engine, options).export
|
|
46
49
|
rescue Foreman::Export::Exception => ex
|
|
47
50
|
error ex.message
|
|
48
51
|
end
|
|
@@ -51,7 +54,20 @@ class Foreman::CLI < Thor
|
|
|
51
54
|
|
|
52
55
|
def check
|
|
53
56
|
error "no processes defined" unless engine.procfile.entries.length > 0
|
|
54
|
-
|
|
57
|
+
puts "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
desc "run COMMAND", "Run a command using your application's environment"
|
|
61
|
+
|
|
62
|
+
def run(*args)
|
|
63
|
+
engine.apply_environment!
|
|
64
|
+
begin
|
|
65
|
+
exec args.join(" ")
|
|
66
|
+
rescue Errno::EACCES
|
|
67
|
+
error "not executable: #{args.first}"
|
|
68
|
+
rescue Errno::ENOENT
|
|
69
|
+
error "command not found: #{args.first}"
|
|
70
|
+
end
|
|
55
71
|
end
|
|
56
72
|
|
|
57
73
|
private ######################################################################
|
|
@@ -68,24 +84,15 @@ private ######################################################################
|
|
|
68
84
|
options[:procfile] || "Procfile"
|
|
69
85
|
end
|
|
70
86
|
|
|
71
|
-
def display(message)
|
|
72
|
-
puts message
|
|
73
|
-
end
|
|
74
|
-
|
|
75
87
|
def error(message)
|
|
76
88
|
puts "ERROR: #{message}"
|
|
77
89
|
exit 1
|
|
78
90
|
end
|
|
79
91
|
|
|
80
|
-
def procfile_exists?(procfile)
|
|
81
|
-
File.exist?(procfile)
|
|
82
|
-
end
|
|
83
|
-
|
|
84
92
|
def options
|
|
85
93
|
original_options = super
|
|
86
94
|
return original_options unless File.exists?(".foreman")
|
|
87
95
|
defaults = YAML::load_file(".foreman") || {}
|
|
88
96
|
Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
|
|
89
97
|
end
|
|
90
|
-
|
|
91
98
|
end
|
data/lib/foreman/engine.rb
CHANGED
|
@@ -2,11 +2,11 @@ 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
|
|
|
@@ -16,11 +16,13 @@ class Foreman::Engine
|
|
|
16
16
|
|
|
17
17
|
extend Term::ANSIColor
|
|
18
18
|
|
|
19
|
-
COLORS = [ cyan, yellow, green, magenta, red
|
|
19
|
+
COLORS = [ cyan, yellow, green, magenta, red, blue,
|
|
20
|
+
intense_cyan, intense_yellow, intense_green, intense_magenta,
|
|
21
|
+
intense_red, intense_blue ]
|
|
20
22
|
|
|
21
23
|
def initialize(procfile, options={})
|
|
22
24
|
@procfile = Foreman::Procfile.new(procfile)
|
|
23
|
-
@directory = File.expand_path(File.dirname(procfile))
|
|
25
|
+
@directory = options[:app_root] || File.expand_path(File.dirname(procfile))
|
|
24
26
|
@options = options
|
|
25
27
|
@environment = read_environment_files(options[:env])
|
|
26
28
|
@output_mutex = Mutex.new
|
|
@@ -57,7 +59,7 @@ private ######################################################################
|
|
|
57
59
|
|
|
58
60
|
procfile.entries.each do |entry|
|
|
59
61
|
reader, writer = IO.pipe
|
|
60
|
-
entry.spawn(concurrency[entry.name], writer, @directory, @environment, base_port).each do |process|
|
|
62
|
+
entry.spawn(concurrency[entry.name], writer, @directory, @environment, port_for(entry, 1, base_port)).each do |process|
|
|
61
63
|
running_processes[process.pid] = process
|
|
62
64
|
readers[process] = reader
|
|
63
65
|
end
|
|
@@ -70,14 +72,23 @@ private ######################################################################
|
|
|
70
72
|
|
|
71
73
|
def kill_all(signal="SIGTERM")
|
|
72
74
|
running_processes.each do |pid, process|
|
|
73
|
-
|
|
75
|
+
info "sending #{signal} to pid #{pid}"
|
|
76
|
+
process.kill signal
|
|
74
77
|
end
|
|
75
78
|
end
|
|
76
79
|
|
|
77
80
|
def terminate_gracefully
|
|
81
|
+
return if @terminating
|
|
82
|
+
@terminating = true
|
|
78
83
|
info "sending SIGTERM to all processes"
|
|
79
84
|
kill_all "SIGTERM"
|
|
80
|
-
Timeout.timeout(5)
|
|
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
|
|
81
92
|
rescue Timeout::Error
|
|
82
93
|
info "sending SIGKILL to all processes"
|
|
83
94
|
kill_all "SIGKILL"
|
|
@@ -85,11 +96,14 @@ private ######################################################################
|
|
|
85
96
|
|
|
86
97
|
def watch_for_output
|
|
87
98
|
Thread.new do
|
|
99
|
+
require "win32console" if Foreman.windows?
|
|
88
100
|
begin
|
|
89
101
|
loop do
|
|
90
102
|
rs, ws = IO.select(readers.values, [], [], 1)
|
|
91
103
|
(rs || []).each do |r|
|
|
92
|
-
|
|
104
|
+
data = r.gets
|
|
105
|
+
next unless data
|
|
106
|
+
ps, message = data.split(",", 2)
|
|
93
107
|
color = colors[ps.split(".").first]
|
|
94
108
|
info message, ps, color
|
|
95
109
|
end
|
|
@@ -106,16 +120,16 @@ private ######################################################################
|
|
|
106
120
|
process = running_processes.delete(pid)
|
|
107
121
|
info "process terminated", process.name
|
|
108
122
|
terminate_gracefully
|
|
109
|
-
kill_all
|
|
110
123
|
rescue Errno::ECHILD
|
|
111
124
|
end
|
|
112
125
|
|
|
113
126
|
def info(message, name="system", color=Term::ANSIColor.white)
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
output = ""
|
|
128
|
+
output += color
|
|
129
|
+
output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
|
|
130
|
+
output += Term::ANSIColor.reset
|
|
131
|
+
output += message.chomp
|
|
132
|
+
puts output
|
|
119
133
|
end
|
|
120
134
|
|
|
121
135
|
def print(message=nil)
|
|
@@ -130,11 +144,6 @@ private ######################################################################
|
|
|
130
144
|
end
|
|
131
145
|
end
|
|
132
146
|
|
|
133
|
-
def error(message)
|
|
134
|
-
puts "ERROR: #{message}"
|
|
135
|
-
exit 1
|
|
136
|
-
end
|
|
137
|
-
|
|
138
147
|
def longest_process_name
|
|
139
148
|
@longest_process_name ||= begin
|
|
140
149
|
longest = procfile.process_names.map { |name| name.length }.sort.last
|
|
@@ -152,7 +161,7 @@ private ######################################################################
|
|
|
152
161
|
end
|
|
153
162
|
|
|
154
163
|
def termtitle(title)
|
|
155
|
-
printf("\033]0;#{title}\007")
|
|
164
|
+
printf("\033]0;#{title}\007") unless Foreman.windows?
|
|
156
165
|
end
|
|
157
166
|
|
|
158
167
|
def running_processes
|
|
@@ -180,7 +189,8 @@ private ######################################################################
|
|
|
180
189
|
def next_color
|
|
181
190
|
@current_color ||= -1
|
|
182
191
|
@current_color += 1
|
|
183
|
-
@current_color
|
|
192
|
+
@current_color = 0 if COLORS.length < @current_color
|
|
193
|
+
COLORS[@current_color]
|
|
184
194
|
end
|
|
185
195
|
|
|
186
196
|
module Env
|
|
@@ -212,6 +222,11 @@ private ######################################################################
|
|
|
212
222
|
def apply_environment!
|
|
213
223
|
@environment.each { |k,v| ENV[k] = v }
|
|
214
224
|
end
|
|
225
|
+
|
|
226
|
+
def error(message)
|
|
227
|
+
puts "ERROR: #{message}"
|
|
228
|
+
exit 1
|
|
229
|
+
end
|
|
215
230
|
end
|
|
216
231
|
|
|
217
232
|
include Env
|
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
13
|
engine.procfile.entries.inject(1) do |index, process|
|
|
16
|
-
1.upto(concurrency[process.name]) do |num|
|
|
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
|
|
data/lib/foreman/export/runit.rb
CHANGED
|
@@ -4,21 +4,19 @@ require "foreman/export"
|
|
|
4
4
|
class Foreman::Export::Runit < Foreman::Export::Base
|
|
5
5
|
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
|
|
6
6
|
|
|
7
|
-
def export
|
|
7
|
+
def export
|
|
8
8
|
error("Must specify a location") unless location
|
|
9
9
|
|
|
10
|
-
app =
|
|
11
|
-
user =
|
|
12
|
-
log_root =
|
|
13
|
-
template_root =
|
|
14
|
-
|
|
15
|
-
concurrency = Foreman::Utils.parse_concurrency(options[:concurrency])
|
|
10
|
+
app = self.app || File.basename(engine.directory)
|
|
11
|
+
user = self.user || app
|
|
12
|
+
log_root = self.log || "/var/log/#{app}"
|
|
13
|
+
template_root = self.template
|
|
16
14
|
|
|
17
15
|
run_template = export_template('runit', 'run.erb', template_root)
|
|
18
16
|
log_run_template = export_template('runit', 'log_run.erb', template_root)
|
|
19
17
|
|
|
20
18
|
engine.procfile.entries.each do |process|
|
|
21
|
-
1.upto(concurrency[process.name]) do |num|
|
|
19
|
+
1.upto(self.concurrency[process.name]) do |num|
|
|
22
20
|
process_directory = "#{location}/#{app}-#{process.name}-#{num}"
|
|
23
21
|
process_env_directory = "#{process_directory}/env"
|
|
24
22
|
process_log_directory = "#{process_directory}/log"
|
|
@@ -29,8 +27,9 @@ class Foreman::Export::Runit < Foreman::Export::Base
|
|
|
29
27
|
|
|
30
28
|
run = ERB.new(run_template).result(binding)
|
|
31
29
|
write_file "#{process_directory}/run", run
|
|
30
|
+
FileUtils.chmod 0755, "#{process_directory}/run"
|
|
32
31
|
|
|
33
|
-
port = engine.port_for(process, num,
|
|
32
|
+
port = engine.port_for(process, num, self.port)
|
|
34
33
|
environment_variables = {'PORT' => port}.
|
|
35
34
|
merge(engine.environment).
|
|
36
35
|
merge(inline_variables(process.command))
|
|
@@ -41,7 +40,7 @@ class Foreman::Export::Runit < Foreman::Export::Base
|
|
|
41
40
|
|
|
42
41
|
log_run = ERB.new(log_run_template).result(binding)
|
|
43
42
|
write_file "#{process_log_directory}/run", log_run
|
|
44
|
-
|
|
43
|
+
FileUtils.chmod 0755, "#{process_log_directory}/run"
|
|
45
44
|
end
|
|
46
45
|
end
|
|
47
46
|
|
|
@@ -50,7 +49,7 @@ class Foreman::Export::Runit < Foreman::Export::Base
|
|
|
50
49
|
private
|
|
51
50
|
def create_directory(location)
|
|
52
51
|
say "creating: #{location}"
|
|
53
|
-
FileUtils.
|
|
52
|
+
FileUtils.mkdir_p(location)
|
|
54
53
|
end
|
|
55
54
|
|
|
56
55
|
def inline_variables(command)
|
|
@@ -3,23 +3,21 @@ require "foreman/export"
|
|
|
3
3
|
|
|
4
4
|
class Foreman::Export::Upstart < 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}*.conf"].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("upstart", "master.conf.erb", template_root)
|
|
24
22
|
master_config = ERB.new(master_template).result(binding)
|
|
25
23
|
write_file "#{location}/#{app}.conf", master_config
|
|
@@ -27,13 +25,13 @@ class Foreman::Export::Upstart < Foreman::Export::Base
|
|
|
27
25
|
process_template = export_template("upstart", "process.conf.erb", template_root)
|
|
28
26
|
|
|
29
27
|
engine.procfile.entries.each do |process|
|
|
30
|
-
next if (conc = concurrency[process.name]) < 1
|
|
28
|
+
next if (conc = self.concurrency[process.name]) < 1
|
|
31
29
|
process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
|
|
32
30
|
process_master_config = ERB.new(process_master_template).result(binding)
|
|
33
31
|
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
|
|
34
32
|
|
|
35
|
-
1.upto(concurrency[process.name]) do |num|
|
|
36
|
-
port = engine.port_for(process, num,
|
|
33
|
+
1.upto(self.concurrency[process.name]) do |num|
|
|
34
|
+
port = engine.port_for(process, num, self.port)
|
|
37
35
|
process_config = ERB.new(process_template).result(binding)
|
|
38
36
|
write_file "#{location}/#{app}-#{process.name}-#{num}.conf", process_config
|
|
39
37
|
end
|
data/lib/foreman/export.rb
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
require "foreman"
|
|
2
|
+
require "foreman/helpers"
|
|
2
3
|
|
|
3
4
|
module Foreman::Export
|
|
5
|
+
extend Foreman::Helpers
|
|
6
|
+
|
|
4
7
|
class Exception < ::Exception; end
|
|
8
|
+
|
|
9
|
+
def self.formatter(format)
|
|
10
|
+
begin
|
|
11
|
+
require "foreman/export/#{ format.tr('-', '_') }"
|
|
12
|
+
classy_format = classify(format)
|
|
13
|
+
formatter = constantize("Foreman::Export::#{ classy_format }")
|
|
14
|
+
rescue NameError => ex
|
|
15
|
+
error "Unknown export format: #{format} (no class Foreman::Export::#{ classy_format })."
|
|
16
|
+
rescue LoadError => ex
|
|
17
|
+
error "Unknown export format: #{format} (unable to load file 'foreman/export/#{ format.tr('-', '_') }')."
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.error(message)
|
|
22
|
+
raise Foreman::Export::Exception.new(message)
|
|
23
|
+
end
|
|
24
|
+
|
|
5
25
|
end
|
|
6
26
|
|
|
27
|
+
|
|
7
28
|
require "foreman/export/base"
|
|
8
29
|
require "foreman/export/inittab"
|
|
9
30
|
require "foreman/export/upstart"
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Foreman::Helpers
|
|
2
|
+
# Copied whole sale from, https://github.com/defunkt/resque/
|
|
3
|
+
|
|
4
|
+
# Given a word with dashes, returns a camel cased version of it.
|
|
5
|
+
#
|
|
6
|
+
# classify('job-name') # => 'JobName'
|
|
7
|
+
def classify(dashed_word)
|
|
8
|
+
dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
|
|
9
|
+
end # Tries to find a constant with the name specified in the argument string:
|
|
10
|
+
|
|
11
|
+
#
|
|
12
|
+
# constantize("Module") # => Module
|
|
13
|
+
# constantize("Test::Unit") # => Test::Unit
|
|
14
|
+
#
|
|
15
|
+
# The name is assumed to be the one of a top-level constant, no matter
|
|
16
|
+
# whether it starts with "::" or not. No lexical context is taken into
|
|
17
|
+
# account:
|
|
18
|
+
#
|
|
19
|
+
# C = 'outside'
|
|
20
|
+
# module M
|
|
21
|
+
# C = 'inside'
|
|
22
|
+
# C # => 'inside'
|
|
23
|
+
# constantize("C") # => 'outside', same as ::C
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# NameError is raised when the constant is unknown.
|
|
27
|
+
def constantize(camel_cased_word)
|
|
28
|
+
camel_cased_word = camel_cased_word.to_s
|
|
29
|
+
|
|
30
|
+
names = camel_cased_word.split('::')
|
|
31
|
+
names.shift if names.empty? || names.first.empty?
|
|
32
|
+
|
|
33
|
+
constant = Object
|
|
34
|
+
names.each do |name|
|
|
35
|
+
args = Module.method(:const_get).arity != 1 ? [false] : []
|
|
36
|
+
|
|
37
|
+
if constant.const_defined?(name, *args)
|
|
38
|
+
constant = constant.const_get(name)
|
|
39
|
+
else
|
|
40
|
+
constant = constant.const_missing(name)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
constant
|
|
44
|
+
end
|
|
45
|
+
end
|