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.
Files changed (61) hide show
  1. data/README.md +39 -0
  2. data/bin/foreman +7 -0
  3. data/bin/runner +36 -0
  4. data/data/example/Procfile +2 -0
  5. data/data/example/Procfile.without_colon +2 -0
  6. data/data/example/error +7 -0
  7. data/data/example/log/neverdie.log +4 -0
  8. data/data/example/ticker +14 -0
  9. data/data/export/bluepill/master.pill.erb +27 -0
  10. data/data/export/runit/log_run.erb +7 -0
  11. data/data/export/runit/run.erb +3 -0
  12. data/data/export/upstart/master.conf.erb +8 -0
  13. data/data/export/upstart/process.conf.erb +5 -0
  14. data/data/export/upstart/process_master.conf.erb +2 -0
  15. data/lib/foreman.rb +25 -0
  16. data/lib/foreman/cli.rb +98 -0
  17. data/lib/foreman/distribution.rb +9 -0
  18. data/lib/foreman/engine.rb +234 -0
  19. data/lib/foreman/export.rb +32 -0
  20. data/lib/foreman/export/base.rb +51 -0
  21. data/lib/foreman/export/bluepill.rb +26 -0
  22. data/lib/foreman/export/inittab.rb +36 -0
  23. data/lib/foreman/export/runit.rb +59 -0
  24. data/lib/foreman/export/upstart.rb +41 -0
  25. data/lib/foreman/helpers.rb +45 -0
  26. data/lib/foreman/process.rb +96 -0
  27. data/lib/foreman/procfile.rb +38 -0
  28. data/lib/foreman/procfile_entry.rb +22 -0
  29. data/lib/foreman/utils.rb +18 -0
  30. data/lib/foreman/version.rb +5 -0
  31. data/man/foreman.1 +222 -0
  32. data/spec/foreman/cli_spec.rb +163 -0
  33. data/spec/foreman/engine_spec.rb +86 -0
  34. data/spec/foreman/export/base_spec.rb +22 -0
  35. data/spec/foreman/export/bluepill_spec.rb +36 -0
  36. data/spec/foreman/export/inittab_spec.rb +40 -0
  37. data/spec/foreman/export/runit_spec.rb +41 -0
  38. data/spec/foreman/export/upstart_spec.rb +87 -0
  39. data/spec/foreman/export_spec.rb +24 -0
  40. data/spec/foreman/helpers_spec.rb +26 -0
  41. data/spec/foreman/process_spec.rb +131 -0
  42. data/spec/foreman_spec.rb +34 -0
  43. data/spec/helper_spec.rb +18 -0
  44. data/spec/resources/export/bluepill/app-concurrency.pill +47 -0
  45. data/spec/resources/export/bluepill/app.pill +44 -0
  46. data/spec/resources/export/inittab/inittab.concurrency +4 -0
  47. data/spec/resources/export/inittab/inittab.default +4 -0
  48. data/spec/resources/export/runit/app-alpha-1-log-run +7 -0
  49. data/spec/resources/export/runit/app-alpha-1-run +3 -0
  50. data/spec/resources/export/runit/app-alpha-2-log-run +7 -0
  51. data/spec/resources/export/runit/app-alpha-2-run +3 -0
  52. data/spec/resources/export/runit/app-bravo-1-log-run +7 -0
  53. data/spec/resources/export/runit/app-bravo-1-run +3 -0
  54. data/spec/resources/export/upstart/app-alpha-1.conf +5 -0
  55. data/spec/resources/export/upstart/app-alpha-2.conf +5 -0
  56. data/spec/resources/export/upstart/app-alpha.conf +2 -0
  57. data/spec/resources/export/upstart/app-bravo-1.conf +5 -0
  58. data/spec/resources/export/upstart/app-bravo.conf +2 -0
  59. data/spec/resources/export/upstart/app.conf +8 -0
  60. data/spec/spec_helper.rb +98 -0
  61. 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