foreman 0.46.0 → 0.50.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.
Files changed (68) hide show
  1. data/README.md +6 -0
  2. data/bin/foreman-runner +3 -7
  3. data/bin/taskman +8 -0
  4. data/data/example/Procfile +4 -3
  5. data/data/example/spawnee +14 -0
  6. data/data/example/spawner +7 -0
  7. data/data/export/bluepill/master.pill.erb +11 -10
  8. data/data/export/launchd/launchd.plist.erb +22 -0
  9. data/data/export/runit/log/run.erb +7 -0
  10. data/data/export/runit/run.erb +2 -2
  11. data/data/export/supervisord/app.conf.erb +12 -12
  12. data/data/export/upstart/master.conf.erb +2 -2
  13. data/data/export/upstart/process.conf.erb +3 -3
  14. data/lib/foreman/cli.rb +59 -23
  15. data/lib/foreman/engine/cli.rb +105 -0
  16. data/lib/foreman/engine.rb +233 -147
  17. data/lib/foreman/env.rb +27 -0
  18. data/lib/foreman/export/base.rb +107 -12
  19. data/lib/foreman/export/bluepill.rb +3 -17
  20. data/lib/foreman/export/inittab.rb +8 -11
  21. data/lib/foreman/export/launchd.rb +15 -0
  22. data/lib/foreman/export/runit.rb +14 -39
  23. data/lib/foreman/export/supervisord.rb +3 -13
  24. data/lib/foreman/export/upstart.rb +9 -27
  25. data/lib/foreman/export.rb +2 -2
  26. data/lib/foreman/process.rb +73 -67
  27. data/lib/foreman/procfile.rb +59 -25
  28. data/lib/foreman/version.rb +1 -1
  29. data/lib/foreman.rb +4 -0
  30. data/man/foreman.1 +5 -1
  31. data/spec/foreman/cli_spec.rb +46 -150
  32. data/spec/foreman/engine_spec.rb +47 -74
  33. data/spec/foreman/export/base_spec.rb +4 -7
  34. data/spec/foreman/export/bluepill_spec.rb +7 -6
  35. data/spec/foreman/export/inittab_spec.rb +7 -7
  36. data/spec/foreman/export/launchd_spec.rb +21 -0
  37. data/spec/foreman/export/runit_spec.rb +12 -17
  38. data/spec/foreman/export/supervisord_spec.rb +7 -56
  39. data/spec/foreman/export/upstart_spec.rb +22 -21
  40. data/spec/foreman/process_spec.rb +27 -110
  41. data/spec/foreman/procfile_spec.rb +26 -16
  42. data/spec/resources/Procfile +4 -0
  43. data/spec/resources/bin/echo +2 -0
  44. data/spec/resources/bin/env +2 -0
  45. data/spec/resources/bin/test +2 -0
  46. data/spec/resources/export/bluepill/app-concurrency.pill +6 -4
  47. data/spec/resources/export/bluepill/app.pill +6 -4
  48. data/spec/resources/export/launchd/launchd-a.default +22 -0
  49. data/spec/resources/export/launchd/launchd-b.default +22 -0
  50. data/spec/resources/export/supervisord/app-alpha-1.conf +24 -0
  51. data/spec/resources/export/supervisord/app-alpha-2.conf +4 -4
  52. data/spec/spec_helper.rb +57 -6
  53. metadata +29 -21
  54. data/data/export/runit/log_run.erb +0 -7
  55. data/lib/foreman/color.rb +0 -40
  56. data/lib/foreman/procfile_entry.rb +0 -26
  57. data/lib/foreman/utils.rb +0 -18
  58. data/spec/foreman/color_spec.rb +0 -31
  59. data/spec/foreman/procfile_entry_spec.rb +0 -13
  60. data/spec/resources/export/supervisord/app-env-with-comma.conf +0 -24
  61. data/spec/resources/export/supervisord/app-env.conf +0 -21
  62. data/spec/resources/export/supervisord/app.conf +0 -24
  63. /data/spec/resources/export/runit/{app-alpha-1-log-run → app-alpha-1/log/run} +0 -0
  64. /data/spec/resources/export/runit/{app-alpha-1-run → app-alpha-1/run} +0 -0
  65. /data/spec/resources/export/runit/{app-alpha-2-log-run → app-alpha-2/log/run} +0 -0
  66. /data/spec/resources/export/runit/{app-alpha-2-run → app-alpha-2/run} +0 -0
  67. /data/spec/resources/export/runit/{app-bravo-1-log-run → app-bravo-1/log/run} +0 -0
  68. /data/spec/resources/export/runit/{app-bravo-1-run → app-bravo-1/run} +0 -0
@@ -3,21 +3,19 @@ require "foreman/export"
3
3
  class Foreman::Export::Inittab < Foreman::Export::Base
4
4
 
5
5
  def export
6
- app = self.app || File.basename(engine.directory)
7
- user = self.user || app
8
- log_root = self.log || "/var/log/#{app}"
6
+ error("Must specify a location") unless location
9
7
 
10
8
  inittab = []
11
9
  inittab << "# ----- foreman #{app} processes -----"
12
10
 
13
- engine.procfile.entries.inject(1) do |index, process|
14
- 1.upto(self.concurrency[process.name]) do |num|
11
+ index = 1
12
+ engine.each_process do |name, process|
13
+ 1.upto(engine.formation[name]) do |num|
15
14
  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'"
15
+ port = engine.port_for(process, num)
16
+ inittab << "#{id}:4:respawn:/bin/su - #{user} -c 'PORT=#{port} #{process.command} >> #{log}/#{name}-#{num}.log 2>&1'"
18
17
  index += 1
19
18
  end
20
- index
21
19
  end
22
20
 
23
21
  inittab << "# ----- end foreman #{app} processes -----"
@@ -27,9 +25,8 @@ class Foreman::Export::Inittab < Foreman::Export::Base
27
25
  if location == "-"
28
26
  puts inittab
29
27
  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)
28
+ say "writing: #{location}"
29
+ File.open(location, "w") { |file| file.puts inittab }
33
30
  end
34
31
  end
35
32
 
@@ -0,0 +1,15 @@
1
+ require "erb"
2
+ require "foreman/export"
3
+
4
+ class Foreman::Export::Launchd < Foreman::Export::Base
5
+
6
+ def export
7
+ super
8
+ engine.each_process do |name, process|
9
+ 1.upto(engine.formation[name]) do |num|
10
+ write_template "launchd/launchd.plist.erb", "#{app}-#{name}-#{num}.plist", binding
11
+ end
12
+ end
13
+ end
14
+
15
+ end
@@ -2,58 +2,33 @@ require "erb"
2
2
  require "foreman/export"
3
3
 
4
4
  class Foreman::Export::Runit < Foreman::Export::Base
5
+
5
6
  ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
6
7
 
7
8
  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
9
+ super
14
10
 
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"
11
+ engine.each_process do |name, process|
12
+ 1.upto(engine.formation[name]) do |num|
13
+ process_directory = "#{app}-#{name}-#{num}"
23
14
 
24
15
  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"
16
+ create_directory "#{process_directory}/env"
17
+ create_directory "#{process_directory}/log"
31
18
 
32
- port = engine.port_for(process, num, self.port)
33
- environment_variables = {'PORT' => port}.
34
- merge(engine.environment).
35
- merge(inline_variables(process.command))
19
+ write_template "runit/run.erb", "#{process_directory}/run", binding
20
+ chmod 0755, "#{process_directory}/run"
36
21
 
37
- environment_variables.each_pair do |var, env|
38
- write_file "#{process_env_directory}/#{var.upcase}", env
22
+ port = engine.port_for(process, num)
23
+ engine.env.merge("PORT" => port.to_s).each do |key, value|
24
+ write_file "#{process_directory}/env/#{key}", value
39
25
  end
40
26
 
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"
27
+ write_template "runit/log/run.erb", "#{process_directory}/log/run", binding
28
+ chmod 0755, "#{process_directory}/log/run"
44
29
  end
45
30
  end
46
31
 
47
32
  end
48
33
 
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
34
  end
@@ -4,23 +4,13 @@ require "foreman/export"
4
4
  class Foreman::Export::Supervisord < Foreman::Export::Base
5
5
 
6
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
7
+ super
15
8
 
16
9
  Dir["#{location}/#{app}*.conf"].each do |file|
17
- say "cleaning up: #{file}"
18
- FileUtils.rm(file)
10
+ clean file
19
11
  end
20
12
 
21
- app_template = export_template("supervisord", "app.conf.erb", template_root)
22
- app_config = ERB.new(app_template, 0, '<').result(binding)
23
- write_file "#{location}/#{app}.conf", app_config
13
+ write_template "supervisord/app.conf.erb", "#{app}.conf", binding
24
14
  end
25
15
 
26
16
  end
@@ -4,40 +4,22 @@ require "foreman/export"
4
4
  class Foreman::Export::Upstart < Foreman::Export::Base
5
5
 
6
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
7
+ super
15
8
 
16
9
  Dir["#{location}/#{app}*.conf"].each do |file|
17
- say "cleaning up: #{file}"
18
- FileUtils.rm(file)
10
+ clean file
19
11
  end
20
12
 
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
13
+ write_template "upstart/master.conf.erb", "#{app}.conf", binding
24
14
 
25
- process_template = export_template("upstart", "process.conf.erb", template_root)
15
+ engine.each_process do |name, process|
16
+ next if engine.formation[name] < 1
17
+ write_template "upstart/process_master.conf.erb", "#{app}-#{name}.conf", binding
26
18
 
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
19
+ 1.upto(engine.formation[name]) do |num|
20
+ port = engine.port_for(process, num)
21
+ write_template "upstart/process.conf.erb", "#{app}-#{name}-#{num}.conf", binding
37
22
  end
38
23
  end
39
-
40
- FileUtils.mkdir_p(log_root) rescue error "could not create #{log_root}"
41
- FileUtils.chown(user, nil, log_root) rescue error "could not chown #{log_root} to #{user}"
42
24
  end
43
25
  end
@@ -1,5 +1,6 @@
1
1
  require "foreman"
2
2
  require "foreman/helpers"
3
+ require "pathname"
3
4
 
4
5
  module Foreman::Export
5
6
  extend Foreman::Helpers
@@ -24,11 +25,10 @@ module Foreman::Export
24
25
 
25
26
  end
26
27
 
27
-
28
28
  require "foreman/export/base"
29
29
  require "foreman/export/inittab"
30
30
  require "foreman/export/upstart"
31
31
  require "foreman/export/bluepill"
32
32
  require "foreman/export/runit"
33
33
  require "foreman/export/supervisord"
34
-
34
+ require "foreman/export/launchd"
@@ -3,94 +3,100 @@ require "rubygems"
3
3
 
4
4
  class Foreman::Process
5
5
 
6
- attr_reader :entry
7
- attr_reader :num
8
- attr_reader :pid
9
- attr_reader :port
6
+ attr_reader :command
7
+ attr_reader :env
10
8
 
11
- def initialize(entry, num, port)
12
- @entry = entry
13
- @num = num
14
- @port = port
15
- end
9
+ # Create a Process
10
+ #
11
+ # @param [String] command The command to run
12
+ # @param [Hash] options
13
+ #
14
+ # @option options [String] :cwd (./) Change to this working directory before executing the process
15
+ # @option options [Hash] :env ({}) Environment variables to set for this process
16
+ #
17
+ def initialize(command, options={})
18
+ @command = command
19
+ @options = options.dup
16
20
 
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
+ @options[:env] ||= {}
21
22
  end
22
23
 
23
- def name
24
- "%s.%s" % [ entry.name, num ]
24
+ # Run a +Process+
25
+ #
26
+ # @param [Hash] options
27
+ #
28
+ # @option options :env ({}) Environment variables to set for this execution
29
+ # @option options :output ($stdout) The output stream
30
+ #
31
+ # @returns [Fixnum] pid The +pid+ of the process
32
+ #
33
+ def run(options={})
34
+ env = options[:env] ? @options[:env].merge(options[:env]) : @options[:env]
35
+ output = options[:output] || $stdout
36
+
37
+ if Foreman.windows?
38
+ Dir.chdir(cwd) do
39
+ expanded_command = command.dup
40
+ env.each do |key, val|
41
+ expanded_command.gsub!("$#{key}", val)
42
+ end
43
+ Process.spawn env, expanded_command, :out => output, :err => output
44
+ end
45
+ elsif Foreman.jruby?
46
+ Dir.chdir(cwd) do
47
+ require "posix/spawn"
48
+ POSIX::Spawn.spawn env, command, :out => output, :err => output
49
+ end
50
+ elsif Foreman.ruby_18?
51
+ Dir.chdir(cwd) do
52
+ fork do
53
+ $stdout.reopen output
54
+ $stderr.reopen output
55
+ env.each { |k,v| ENV[k] = v }
56
+ exec command
57
+ end
58
+ end
59
+ else
60
+ Dir.chdir(cwd) do
61
+ Process.spawn env, command, :out => output, :err => output
62
+ end
63
+ end
25
64
  end
26
65
 
66
+ # Send a signal to this +Process+
67
+ #
68
+ # @param [String] signal The signal to send
69
+ #
27
70
  def kill(signal)
28
- pid && Process.kill(signal, pid)
71
+ if Foreman.windows?
72
+ pid && Process.kill(signal, pid)
73
+ else
74
+ pid && Process.kill("-#{signal}", pid)
75
+ end
29
76
  rescue Errno::ESRCH
30
77
  false
31
78
  end
32
79
 
33
- def detach
34
- pid && Process.detach(pid)
35
- end
36
-
80
+ # Test whether or not this +Process+ is still running
81
+ #
82
+ # @returns [Boolean]
83
+ #
37
84
  def alive?
38
85
  kill(0)
39
86
  end
40
87
 
88
+ # Test whether or not this +Process+ has terminated
89
+ #
90
+ # @returns [Boolean]
91
+ #
41
92
  def dead?
42
93
  !alive?
43
94
  end
44
95
 
45
96
  private
46
97
 
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 ]
98
+ def cwd
99
+ @options[:cwd] || "."
83
100
  end
84
101
 
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
102
  end
@@ -1,56 +1,90 @@
1
1
  require "foreman"
2
- require "foreman/procfile_entry"
3
2
 
4
- # A valid Procfile entry is captured by this regex.
5
- # All other lines are ignored.
3
+ # Reads and writes Procfiles
4
+ #
5
+ # A valid Procfile entry is captured by this regex:
6
6
  #
7
- # /^([A-Za-z0-9_]+):\s*(.+)$/
7
+ # /^([A-Za-z0-9_]+):\s*(.+)$/
8
8
  #
9
- # $1 = name
10
- # $2 = command
9
+ # All other lines are ignored.
11
10
  #
12
11
  class Foreman::Procfile
13
12
 
14
- attr_reader :entries
15
-
13
+ # Initialize a Procfile
14
+ #
15
+ # @param [String] filename (nil) An optional filename to read from
16
+ #
16
17
  def initialize(filename=nil)
17
18
  @entries = []
18
19
  load(filename) if filename
19
20
  end
20
21
 
22
+ # Yield each +Procfile+ entry in order
23
+ #
24
+ def entries(&blk)
25
+ @entries.each do |(name, command)|
26
+ yield name, command
27
+ end
28
+ end
29
+
30
+ # Retrieve a +Procfile+ command by name
31
+ #
32
+ # @param [String] name The name of the Procfile entry to retrieve
33
+ #
21
34
  def [](name)
22
- entries.detect { |entry| entry.name == name }
35
+ @entries.detect { |n,c| name == n }.last
36
+ end
37
+
38
+ # Create a +Procfile+ entry
39
+ #
40
+ # @param [String] name The name of the +Procfile+ entry to create
41
+ # @param [String] command The command of the +Procfile+ entry to create
42
+ #
43
+ def []=(name, command)
44
+ delete name
45
+ @entries << [name, command]
23
46
  end
24
47
 
25
- def process_names
26
- entries.map(&:name)
48
+ # Remove a +Procfile+ entry
49
+ #
50
+ # @param [String] name The name of the +Procfile+ entry to remove
51
+ #
52
+ def delete(name)
53
+ @entries.reject! { |n,c| name == n }
27
54
  end
28
55
 
56
+ # Load a Procfile from a file
57
+ #
58
+ # @param [String] filename The filename of the +Procfile+ to load
59
+ #
29
60
  def load(filename)
30
- entries.clear
31
- parse_procfile(filename)
61
+ @entries.replace parse(filename)
32
62
  end
33
63
 
34
- def write(filename)
35
- File.open(filename, 'w') do |io|
36
- entries.each do |ent|
37
- io.puts(ent)
38
- end
64
+ # Save a Procfile to a file
65
+ #
66
+ # @param [String] filename Save the +Procfile+ to this file
67
+ #
68
+ def save(filename)
69
+ File.open(filename, 'w') do |file|
70
+ file.puts self.to_s
39
71
  end
40
72
  end
41
73
 
42
- def <<(entry)
43
- entries << Foreman::ProcfileEntry.new(*entry)
44
- self
74
+ # Get the +Procfile+ as a +String+
75
+ #
76
+ def to_s
77
+ @entries.map do |name, command|
78
+ [ name, command ].join(": ")
79
+ end.join("\n")
45
80
  end
46
81
 
82
+ private
47
83
 
48
- protected
49
-
50
- def parse_procfile(filename)
84
+ def parse(filename)
51
85
  File.read(filename).split("\n").map do |line|
52
86
  if line =~ /^([A-Za-z0-9_]+):\s*(.+)$/
53
- self << [ $1, $2 ]
87
+ [$1, $2]
54
88
  end
55
89
  end.compact
56
90
  end
@@ -1,5 +1,5 @@
1
1
  module Foreman
2
2
 
3
- VERSION = "0.46.0"
3
+ VERSION = "0.50.0"
4
4
 
5
5
  end
data/lib/foreman.rb CHANGED
@@ -12,6 +12,10 @@ module Foreman
12
12
  defined?(RUBY_PLATFORM) and RUBY_PLATFORM == "java"
13
13
  end
14
14
 
15
+ def self.ruby_18?
16
+ defined?(RUBY_VERSION) and RUBY_VERSION =~ /^1\.8\.\d+/
17
+ end
18
+
15
19
  def self.windows?
16
20
  defined?(RUBY_PLATFORM) and RUBY_PLATFORM =~ /(win|w)32$/
17
21
  end
data/man/foreman.1 CHANGED
@@ -1,7 +1,7 @@
1
1
  .\" generated with Ronn/v0.7.3
2
2
  .\" http://github.com/rtomayko/ronn/tree/0.7.3
3
3
  .
4
- .TH "FOREMAN" "1" "April 2012" "Foreman 0.45.0" "Foreman Manual"
4
+ .TH "FOREMAN" "1" "July 2012" "Foreman 0.50.0" "Foreman Manual"
5
5
  .
6
6
  .SH "NAME"
7
7
  \fBforeman\fR \- manage Procfile\-based applications
@@ -46,6 +46,10 @@ Specify an alternate Procfile to load, implies \fB\-d\fR at the Procfile root\.
46
46
  \fB\-p\fR, \fB\-\-port\fR
47
47
  Specify which port to use as the base for this application\. Should be a multiple of 1000\.
48
48
  .
49
+ .TP
50
+ \fB\-t\fR, \fB\-\-tmux\fR
51
+ Runs the processes in a tmux session\. Creates one window for each process and an extra window containing the output of each window (requires gawk)\.
52
+ .
49
53
  .P
50
54
  \fBforeman run\fR is used to run one\-off commands using the same environment as your defined processes\.
51
55
  .