foreman 0.37.0-mingw32
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/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
|