foreman 0.37.0-mingw32

Sign up to get free protection for your applications and to get access to all the features.
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