foreman 0.37.0-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +39 -0
- data/bin/foreman +7 -0
- data/bin/runner +36 -0
- data/data/example/Procfile +2 -0
- data/data/example/Procfile.without_colon +2 -0
- data/data/example/error +7 -0
- data/data/example/log/neverdie.log +4 -0
- data/data/example/ticker +14 -0
- data/data/export/bluepill/master.pill.erb +27 -0
- data/data/export/runit/log_run.erb +7 -0
- data/data/export/runit/run.erb +3 -0
- data/data/export/upstart/master.conf.erb +8 -0
- data/data/export/upstart/process.conf.erb +5 -0
- data/data/export/upstart/process_master.conf.erb +2 -0
- data/lib/foreman.rb +25 -0
- data/lib/foreman/cli.rb +98 -0
- data/lib/foreman/distribution.rb +9 -0
- data/lib/foreman/engine.rb +234 -0
- data/lib/foreman/export.rb +32 -0
- data/lib/foreman/export/base.rb +51 -0
- data/lib/foreman/export/bluepill.rb +26 -0
- data/lib/foreman/export/inittab.rb +36 -0
- data/lib/foreman/export/runit.rb +59 -0
- data/lib/foreman/export/upstart.rb +41 -0
- data/lib/foreman/helpers.rb +45 -0
- data/lib/foreman/process.rb +96 -0
- data/lib/foreman/procfile.rb +38 -0
- data/lib/foreman/procfile_entry.rb +22 -0
- data/lib/foreman/utils.rb +18 -0
- data/lib/foreman/version.rb +5 -0
- data/man/foreman.1 +222 -0
- data/spec/foreman/cli_spec.rb +163 -0
- data/spec/foreman/engine_spec.rb +86 -0
- data/spec/foreman/export/base_spec.rb +22 -0
- data/spec/foreman/export/bluepill_spec.rb +36 -0
- data/spec/foreman/export/inittab_spec.rb +40 -0
- data/spec/foreman/export/runit_spec.rb +41 -0
- data/spec/foreman/export/upstart_spec.rb +87 -0
- data/spec/foreman/export_spec.rb +24 -0
- data/spec/foreman/helpers_spec.rb +26 -0
- data/spec/foreman/process_spec.rb +131 -0
- data/spec/foreman_spec.rb +34 -0
- data/spec/helper_spec.rb +18 -0
- data/spec/resources/export/bluepill/app-concurrency.pill +47 -0
- data/spec/resources/export/bluepill/app.pill +44 -0
- data/spec/resources/export/inittab/inittab.concurrency +4 -0
- data/spec/resources/export/inittab/inittab.default +4 -0
- data/spec/resources/export/runit/app-alpha-1-log-run +7 -0
- data/spec/resources/export/runit/app-alpha-1-run +3 -0
- data/spec/resources/export/runit/app-alpha-2-log-run +7 -0
- data/spec/resources/export/runit/app-alpha-2-run +3 -0
- data/spec/resources/export/runit/app-bravo-1-log-run +7 -0
- data/spec/resources/export/runit/app-bravo-1-run +3 -0
- data/spec/resources/export/upstart/app-alpha-1.conf +5 -0
- data/spec/resources/export/upstart/app-alpha-2.conf +5 -0
- data/spec/resources/export/upstart/app-alpha.conf +2 -0
- data/spec/resources/export/upstart/app-bravo-1.conf +5 -0
- data/spec/resources/export/upstart/app-bravo.conf +2 -0
- data/spec/resources/export/upstart/app.conf +8 -0
- data/spec/spec_helper.rb +98 -0
- metadata +138 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
require "foreman"
|
2
|
+
require "foreman/helpers"
|
3
|
+
|
4
|
+
module Foreman::Export
|
5
|
+
extend Foreman::Helpers
|
6
|
+
|
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
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
require "foreman/export/base"
|
29
|
+
require "foreman/export/inittab"
|
30
|
+
require "foreman/export/upstart"
|
31
|
+
require "foreman/export/bluepill"
|
32
|
+
require "foreman/export/runit"
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require "foreman/export"
|
2
|
+
require "foreman/utils"
|
3
|
+
|
4
|
+
class Foreman::Export::Base
|
5
|
+
|
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])
|
17
|
+
end
|
18
|
+
|
19
|
+
def export
|
20
|
+
raise "export method must be overridden"
|
21
|
+
end
|
22
|
+
|
23
|
+
private ######################################################################
|
24
|
+
|
25
|
+
def error(message)
|
26
|
+
raise Foreman::Export::Exception.new(message)
|
27
|
+
end
|
28
|
+
|
29
|
+
def say(message)
|
30
|
+
puts "[foreman export] %s" % message
|
31
|
+
end
|
32
|
+
|
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
|
41
|
+
end
|
42
|
+
|
43
|
+
def write_file(filename, contents)
|
44
|
+
say "writing: #{filename}"
|
45
|
+
|
46
|
+
File.open(filename, "w") do |file|
|
47
|
+
file.puts contents
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "erb"
|
2
|
+
require "foreman/export"
|
3
|
+
|
4
|
+
class Foreman::Export::Bluepill < Foreman::Export::Base
|
5
|
+
|
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
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "foreman/export"
|
2
|
+
|
3
|
+
class Foreman::Export::Inittab < Foreman::Export::Base
|
4
|
+
|
5
|
+
def export
|
6
|
+
app = self.app || File.basename(engine.directory)
|
7
|
+
user = self.user || app
|
8
|
+
log_root = self.log || "/var/log/#{app}"
|
9
|
+
|
10
|
+
inittab = []
|
11
|
+
inittab << "# ----- foreman #{app} processes -----"
|
12
|
+
|
13
|
+
engine.procfile.entries.inject(1) do |index, process|
|
14
|
+
1.upto(self.concurrency[process.name]) do |num|
|
15
|
+
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'"
|
18
|
+
index += 1
|
19
|
+
end
|
20
|
+
index
|
21
|
+
end
|
22
|
+
|
23
|
+
inittab << "# ----- end foreman #{app} processes -----"
|
24
|
+
|
25
|
+
inittab = inittab.join("\n") + "\n"
|
26
|
+
|
27
|
+
if location == "-"
|
28
|
+
puts inittab
|
29
|
+
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)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1,59 @@
|
|
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
|
8
|
+
error("Must specify a location") unless location
|
9
|
+
|
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
|
14
|
+
|
15
|
+
run_template = export_template('runit', 'run.erb', template_root)
|
16
|
+
log_run_template = export_template('runit', 'log_run.erb', template_root)
|
17
|
+
|
18
|
+
engine.procfile.entries.each do |process|
|
19
|
+
1.upto(self.concurrency[process.name]) do |num|
|
20
|
+
process_directory = "#{location}/#{app}-#{process.name}-#{num}"
|
21
|
+
process_env_directory = "#{process_directory}/env"
|
22
|
+
process_log_directory = "#{process_directory}/log"
|
23
|
+
|
24
|
+
create_directory process_directory
|
25
|
+
create_directory process_env_directory
|
26
|
+
create_directory process_log_directory
|
27
|
+
|
28
|
+
run = ERB.new(run_template).result(binding)
|
29
|
+
write_file "#{process_directory}/run", run
|
30
|
+
FileUtils.chmod 0755, "#{process_directory}/run"
|
31
|
+
|
32
|
+
port = engine.port_for(process, num, self.port)
|
33
|
+
environment_variables = {'PORT' => port}.
|
34
|
+
merge(engine.environment).
|
35
|
+
merge(inline_variables(process.command))
|
36
|
+
|
37
|
+
environment_variables.each_pair do |var, env|
|
38
|
+
write_file "#{process_env_directory}/#{var.upcase}", env
|
39
|
+
end
|
40
|
+
|
41
|
+
log_run = ERB.new(log_run_template).result(binding)
|
42
|
+
write_file "#{process_log_directory}/run", log_run
|
43
|
+
FileUtils.chmod 0755, "#{process_log_directory}/run"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def create_directory(location)
|
51
|
+
say "creating: #{location}"
|
52
|
+
FileUtils.mkdir_p(location)
|
53
|
+
end
|
54
|
+
|
55
|
+
def inline_variables(command)
|
56
|
+
variable_name_regex =
|
57
|
+
Hash[*command.scan(ENV_VARIABLE_REGEX).flatten]
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "erb"
|
2
|
+
require "foreman/export"
|
3
|
+
|
4
|
+
class Foreman::Export::Upstart < Foreman::Export::Base
|
5
|
+
|
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}*.conf"].each do |file|
|
17
|
+
say "cleaning up: #{file}"
|
18
|
+
FileUtils.rm(file)
|
19
|
+
end
|
20
|
+
|
21
|
+
master_template = export_template("upstart", "master.conf.erb", template_root)
|
22
|
+
master_config = ERB.new(master_template).result(binding)
|
23
|
+
write_file "#{location}/#{app}.conf", master_config
|
24
|
+
|
25
|
+
process_template = export_template("upstart", "process.conf.erb", template_root)
|
26
|
+
|
27
|
+
engine.procfile.entries.each do |process|
|
28
|
+
next if (conc = self.concurrency[process.name]) < 1
|
29
|
+
process_master_template = export_template("upstart", "process_master.conf.erb", template_root)
|
30
|
+
process_master_config = ERB.new(process_master_template).result(binding)
|
31
|
+
write_file "#{location}/#{app}-#{process.name}.conf", process_master_config
|
32
|
+
|
33
|
+
1.upto(self.concurrency[process.name]) do |num|
|
34
|
+
port = engine.port_for(process, num, self.port)
|
35
|
+
process_config = ERB.new(process_template).result(binding)
|
36
|
+
write_file "#{location}/#{app}-#{process.name}-#{num}.conf", process_config
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -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
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "foreman"
|
2
|
+
require "rubygems"
|
3
|
+
|
4
|
+
class Foreman::Process
|
5
|
+
|
6
|
+
attr_reader :entry
|
7
|
+
attr_reader :num
|
8
|
+
attr_reader :pid
|
9
|
+
attr_reader :port
|
10
|
+
|
11
|
+
def initialize(entry, num, port)
|
12
|
+
@entry = entry
|
13
|
+
@num = num
|
14
|
+
@port = port
|
15
|
+
end
|
16
|
+
|
17
|
+
def run(pipe, basedir, environment)
|
18
|
+
with_environment(environment.merge("PORT" => port.to_s)) do
|
19
|
+
run_process basedir, entry.command, pipe
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def name
|
24
|
+
"%s.%s" % [ entry.name, num ]
|
25
|
+
end
|
26
|
+
|
27
|
+
def kill(signal)
|
28
|
+
pid && Process.kill(signal, pid)
|
29
|
+
rescue Errno::ESRCH
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def detach
|
34
|
+
pid && Process.detach(pid)
|
35
|
+
end
|
36
|
+
|
37
|
+
def alive?
|
38
|
+
kill(0)
|
39
|
+
end
|
40
|
+
|
41
|
+
def dead?
|
42
|
+
!alive?
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def fork_with_io(command, basedir)
|
48
|
+
reader, writer = IO.pipe
|
49
|
+
command = replace_command_env(command)
|
50
|
+
pid = if Foreman.windows?
|
51
|
+
Dir.chdir(basedir) do
|
52
|
+
Process.spawn command, :out => writer, :err => writer
|
53
|
+
end
|
54
|
+
elsif Foreman.jruby?
|
55
|
+
require "posix/spawn"
|
56
|
+
POSIX::Spawn.spawn(Foreman.runner, "-d", basedir, command, {
|
57
|
+
:out => writer, :err => writer
|
58
|
+
})
|
59
|
+
else
|
60
|
+
fork do
|
61
|
+
writer.sync = true
|
62
|
+
$stdout.reopen writer
|
63
|
+
$stderr.reopen writer
|
64
|
+
reader.close
|
65
|
+
exec Foreman.runner, "-d", basedir, command
|
66
|
+
end
|
67
|
+
end
|
68
|
+
[ reader, pid ]
|
69
|
+
end
|
70
|
+
|
71
|
+
def run_process(basedir, command, pipe)
|
72
|
+
io, @pid = fork_with_io(command, basedir)
|
73
|
+
output pipe, "started with pid %d" % @pid
|
74
|
+
Thread.new do
|
75
|
+
until io.eof?
|
76
|
+
output pipe, io.gets
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def output(pipe, message)
|
82
|
+
pipe.puts "%s,%s" % [ name, message ]
|
83
|
+
end
|
84
|
+
|
85
|
+
def replace_command_env(command)
|
86
|
+
command.gsub(/\$(\w+)/) { |e| ENV[e[1..-1]] }
|
87
|
+
end
|
88
|
+
|
89
|
+
def with_environment(environment)
|
90
|
+
original = ENV.to_hash
|
91
|
+
ENV.update environment
|
92
|
+
yield
|
93
|
+
ensure
|
94
|
+
ENV.replace original
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "foreman"
|
2
|
+
require "foreman/procfile_entry"
|
3
|
+
|
4
|
+
# A valid Procfile entry is captured by this regex.
|
5
|
+
# All other lines are ignored.
|
6
|
+
#
|
7
|
+
# /^([A-Za-z0-9_]+):\s*(.+)$/
|
8
|
+
#
|
9
|
+
# $1 = name
|
10
|
+
# $2 = command
|
11
|
+
#
|
12
|
+
class Foreman::Procfile
|
13
|
+
|
14
|
+
attr_reader :entries
|
15
|
+
|
16
|
+
def initialize(filename)
|
17
|
+
@entries = parse_procfile(filename)
|
18
|
+
end
|
19
|
+
|
20
|
+
def [](name)
|
21
|
+
entries.detect { |entry| entry.name == name }
|
22
|
+
end
|
23
|
+
|
24
|
+
def process_names
|
25
|
+
entries.map(&:name)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def parse_procfile(filename)
|
31
|
+
File.read(filename).split("\n").map do |line|
|
32
|
+
if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
|
33
|
+
Foreman::ProcfileEntry.new($1, $2)
|
34
|
+
end
|
35
|
+
end.compact
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "foreman"
|
2
|
+
|
3
|
+
class Foreman::ProcfileEntry
|
4
|
+
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :command
|
7
|
+
attr_accessor :color
|
8
|
+
|
9
|
+
def initialize(name, command)
|
10
|
+
@name = name
|
11
|
+
@command = command
|
12
|
+
end
|
13
|
+
|
14
|
+
def spawn(num, pipe, basedir, environment, base_port)
|
15
|
+
(1..num).to_a.map do |n|
|
16
|
+
process = Foreman::Process.new(self, n, base_port + (n-1))
|
17
|
+
process.run(pipe, basedir, environment)
|
18
|
+
process
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|