foreman 0.47.0 → 0.48.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/bin/taskman +8 -0
  2. data/data/example/Procfile +4 -3
  3. data/data/example/spawnee +14 -0
  4. data/data/example/spawner +7 -0
  5. data/data/export/bluepill/master.pill.erb +10 -10
  6. data/data/export/launchd/launchd.plist.erb +3 -3
  7. data/data/export/runit/log/run.erb +7 -0
  8. data/data/export/runit/run.erb +2 -2
  9. data/data/export/supervisord/app.conf.erb +12 -12
  10. data/data/export/upstart/master.conf.erb +2 -2
  11. data/data/export/upstart/process.conf.erb +3 -3
  12. data/lib/foreman/cli.rb +49 -21
  13. data/lib/foreman/engine.rb +208 -148
  14. data/lib/foreman/engine/cli.rb +98 -0
  15. data/lib/foreman/env.rb +27 -0
  16. data/lib/foreman/export.rb +0 -1
  17. data/lib/foreman/export/base.rb +58 -35
  18. data/lib/foreman/export/bluepill.rb +3 -17
  19. data/lib/foreman/export/inittab.rb +8 -11
  20. data/lib/foreman/export/launchd.rb +4 -16
  21. data/lib/foreman/export/runit.rb +14 -39
  22. data/lib/foreman/export/supervisord.rb +3 -13
  23. data/lib/foreman/export/upstart.rb +9 -27
  24. data/lib/foreman/process.rb +56 -67
  25. data/lib/foreman/procfile.rb +59 -25
  26. data/lib/foreman/version.rb +1 -1
  27. data/man/foreman.1 +4 -0
  28. data/spec/foreman/cli_spec.rb +38 -152
  29. data/spec/foreman/engine_spec.rb +46 -80
  30. data/spec/foreman/export/base_spec.rb +4 -7
  31. data/spec/foreman/export/bluepill_spec.rb +7 -6
  32. data/spec/foreman/export/inittab_spec.rb +7 -7
  33. data/spec/foreman/export/launchd_spec.rb +4 -7
  34. data/spec/foreman/export/runit_spec.rb +12 -17
  35. data/spec/foreman/export/supervisord_spec.rb +7 -56
  36. data/spec/foreman/export/upstart_spec.rb +18 -23
  37. data/spec/foreman/process_spec.rb +27 -124
  38. data/spec/foreman/procfile_spec.rb +26 -16
  39. data/spec/resources/Procfile +4 -0
  40. data/spec/resources/bin/echo +2 -0
  41. data/spec/resources/bin/env +2 -0
  42. data/spec/resources/bin/test +2 -0
  43. data/spec/resources/export/bluepill/app-concurrency.pill +4 -4
  44. data/spec/resources/export/bluepill/app.pill +4 -4
  45. data/spec/resources/export/runit/{app-alpha-1-log-run → app-alpha-1/log/run} +0 -0
  46. data/spec/resources/export/runit/{app-alpha-1-run → app-alpha-1/run} +0 -0
  47. data/spec/resources/export/runit/{app-alpha-2-log-run → app-alpha-2/log/run} +0 -0
  48. data/spec/resources/export/runit/{app-alpha-2-run → app-alpha-2/run} +0 -0
  49. data/spec/resources/export/runit/{app-bravo-1-log-run → app-bravo-1/log/run} +0 -0
  50. data/spec/resources/export/runit/{app-bravo-1-run → app-bravo-1/run} +0 -0
  51. data/spec/resources/export/supervisord/app-alpha-1.conf +24 -0
  52. data/spec/resources/export/supervisord/app-alpha-2.conf +4 -4
  53. data/spec/spec_helper.rb +58 -6
  54. metadata +24 -22
  55. data/data/export/runit/log_run.erb +0 -7
  56. data/lib/foreman/color.rb +0 -40
  57. data/lib/foreman/procfile_entry.rb +0 -26
  58. data/lib/foreman/utils.rb +0 -18
  59. data/spec/foreman/color_spec.rb +0 -31
  60. data/spec/foreman/procfile_entry_spec.rb +0 -13
  61. data/spec/resources/export/supervisord/app-env-with-comma.conf +0 -24
  62. data/spec/resources/export/supervisord/app-env.conf +0 -21
  63. data/spec/resources/export/supervisord/app.conf +0 -24
@@ -0,0 +1,98 @@
1
+ require "foreman/engine"
2
+
3
+ class Foreman::Engine::CLI < Foreman::Engine
4
+
5
+ module Color
6
+
7
+ ANSI = {
8
+ :reset => 0,
9
+ :black => 30,
10
+ :red => 31,
11
+ :green => 32,
12
+ :yellow => 33,
13
+ :blue => 34,
14
+ :magenta => 35,
15
+ :cyan => 36,
16
+ :white => 37,
17
+ :bright_black => 30,
18
+ :bright_red => 31,
19
+ :bright_green => 32,
20
+ :bright_yellow => 33,
21
+ :bright_blue => 34,
22
+ :bright_magenta => 35,
23
+ :bright_cyan => 36,
24
+ :bright_white => 37,
25
+ }
26
+
27
+ def self.enable(io)
28
+ io.extend(self)
29
+ end
30
+
31
+ def color?
32
+ return false unless self.respond_to?(:isatty)
33
+ self.isatty && ENV["TERM"]
34
+ end
35
+
36
+ def color(name)
37
+ return "" unless color?
38
+ return "" unless ansi = ANSI[name.to_sym]
39
+ "\e[#{ansi}m"
40
+ end
41
+
42
+ end
43
+
44
+ FOREMAN_COLORS = %w( cyan yellow green magenta red blue intense_cyan intense_yellow
45
+ intense_green intense_magenta intense_red, intense_blue )
46
+
47
+ def startup
48
+ @colors = map_colors
49
+ proctitle "foreman: master"
50
+ end
51
+
52
+ def output(name, data)
53
+ data.to_s.chomp.split("\n").each do |message|
54
+ Color.enable($stdout) unless $stdout.respond_to?(:color?)
55
+ output = ""
56
+ output += $stdout.color(@colors[name.split(".").first].to_sym)
57
+ output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
58
+ output += $stdout.color(:reset)
59
+ output += message
60
+ $stdout.puts output
61
+ end
62
+ end
63
+
64
+ def shutdown
65
+ end
66
+
67
+ private
68
+
69
+ def name_padding
70
+ @name_padding ||= begin
71
+ index_padding = @names.values.map { |n| formation[n] }.max.to_s.length + 1
72
+ name_padding = @names.values.map { |n| n.length + index_padding }.sort.last
73
+ [ 6, name_padding ].max
74
+ end
75
+ end
76
+
77
+ def pad_process_name(name)
78
+ name.ljust(name_padding, " ")
79
+ end
80
+
81
+ def map_colors
82
+ colors = Hash.new("white")
83
+ @names.values.each_with_index do |name, index|
84
+ colors[name] = FOREMAN_COLORS[index % FOREMAN_COLORS.length]
85
+ end
86
+ colors["system"] = "intense_white"
87
+ colors
88
+ end
89
+
90
+ def proctitle(title)
91
+ $0 = title
92
+ end
93
+
94
+ def termtitle(title)
95
+ printf("\033]0;#{title}\007") unless Foreman.windows?
96
+ end
97
+
98
+ end
@@ -0,0 +1,27 @@
1
+ require "foreman"
2
+
3
+ class Foreman::Env
4
+
5
+ attr_reader :entries
6
+
7
+ def initialize(filename)
8
+ @entries = File.read(filename).split("\n").inject({}) do |ax, line|
9
+ if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
10
+ key = $1
11
+ case val = $2
12
+ when /\A'(.*)'\z/ then ax[key] = $1
13
+ when /\A"(.*)"\z/ then ax[key] = $1.gsub(/\\(.)/, '\1')
14
+ else ax[key] = val
15
+ end
16
+ end
17
+ ax
18
+ end
19
+ end
20
+
21
+ def entries
22
+ @entries.each do |key, value|
23
+ yield key, value
24
+ end
25
+ end
26
+
27
+ end
@@ -24,7 +24,6 @@ module Foreman::Export
24
24
 
25
25
  end
26
26
 
27
-
28
27
  require "foreman/export/base"
29
28
  require "foreman/export/inittab"
30
29
  require "foreman/export/upstart"
@@ -1,23 +1,37 @@
1
1
  require "foreman/export"
2
- require "foreman/utils"
2
+ require "shellwords"
3
3
 
4
4
  class Foreman::Export::Base
5
5
 
6
- attr_reader :location, :engine, :app, :log, :port, :user, :template, :concurrency
6
+ attr_reader :location
7
+ attr_reader :engine
8
+ attr_reader :options
9
+ attr_reader :formation
7
10
 
8
11
  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])
12
+ @location = location
13
+ @engine = engine
14
+ @options = options.dup
15
+ @formation = engine.formation
17
16
  end
18
17
 
19
18
  def export
20
- raise "export method must be overridden"
19
+ error("Must specify a location") unless location
20
+ FileUtils.mkdir_p(location) rescue error("Could not create: #{location}")
21
+ FileUtils.mkdir_p(log) rescue error("Could not create: #{log}")
22
+ FileUtils.chown(user, nil, log) rescue error("Could not chown #{log} to #{user}")
23
+ end
24
+
25
+ def app
26
+ options[:app] || "app"
27
+ end
28
+
29
+ def log
30
+ options[:log] || "/var/log/#{app}"
31
+ end
32
+
33
+ def user
34
+ options[:user] || app
21
35
  end
22
36
 
23
37
  private ######################################################################
@@ -29,38 +43,47 @@ private ######################################################################
29
43
  def say(message)
30
44
  puts "[foreman export] %s" % message
31
45
  end
46
+
47
+ def clean(filename)
48
+ return unless File.exists?(filename)
49
+ say "cleaning up: #{filename}"
50
+ FileUtils.rm(filename)
51
+ end
32
52
 
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
53
+ def shell_quote(value)
54
+ '"' + Shellwords.escape(value) + '"'
55
+ end
56
+
57
+ def export_template(name)
58
+ name_without_first = name.split("/")[1..-1].join("/")
59
+ matchers = []
60
+ matchers << File.join(options[:template], name_without_first) if options[:template]
61
+ matchers << File.expand_path("~/.foreman/templates/#{name}")
62
+ matchers << File.expand_path("../../../../data/export/#{name}", __FILE__)
63
+ File.read(matchers.detect { |m| File.exists?(m) })
64
+ end
65
+
66
+ def write_template(name, target, binding)
67
+ compiled = ERB.new(export_template(name)).result(binding)
68
+ write_file target, compiled
69
+ end
70
+
71
+ def chmod(mode, file)
72
+ say "setting #{file} to mode #{mode}"
73
+ FileUtils.chmod mode, File.join(location, file)
74
+ end
75
+
76
+ def create_directory(dir)
77
+ say "creating: #{dir}"
78
+ FileUtils.mkdir_p(File.join(location, dir))
41
79
  end
42
80
 
43
81
  def write_file(filename, contents)
44
82
  say "writing: #{filename}"
45
83
 
46
- File.open(filename, "w") do |file|
84
+ File.open(File.join(location, filename), "w") do |file|
47
85
  file.puts contents
48
86
  end
49
87
  end
50
88
 
51
- # Quote a string to be used on the command line. Backslashes are escapde to \\ and quotes
52
- # escaped to \"
53
- #
54
- # str - string to be quoted
55
- #
56
- # Examples
57
- #
58
- # shell_quote("FB|123\"\\1")
59
- # # => "\"FB|123\"\\"\\\\1\""
60
- #
61
- # Returns the the escaped string surrounded by quotes
62
- def shell_quote(str)
63
- "\"#{str.gsub(/\\/){ '\\\\' }.gsub(/["]/){ "\\\"" }}\""
64
- end
65
-
66
89
  end
@@ -4,23 +4,9 @@ require "foreman/export"
4
4
  class Foreman::Export::Bluepill < 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
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
7
+ super
8
+ clean "#{location}/#{app}.pill"
9
+ write_template "bluepill/master.pill.erb", "#{app}.pill", binding
24
10
  end
25
11
 
26
12
  end
@@ -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
 
@@ -4,24 +4,12 @@ require "foreman/export"
4
4
  class Foreman::Export::Launchd < Foreman::Export::Base
5
5
 
6
6
  def export
7
- error("Must specify a location") unless location
8
-
9
- app = self.app || File.basename(engine.directory)
10
- user = self.user || app
11
- log_root = self.log || "/var/log/#{app}"
12
- template_root = self.template
13
-
14
- FileUtils.mkdir_p(location)
15
-
16
- engine.procfile.entries.each do |process|
17
- 1.upto(self.concurrency[process.name]) do |num|
18
-
19
- master_template = export_template("launchd", "launchd.plist.erb", template_root)
20
- master_config = ERB.new(master_template).result(binding)
21
- write_file "#{location}/#{app}-#{process.name}-#{num}.plist", master_config
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
22
11
  end
23
12
  end
24
-
25
13
  end
26
14
 
27
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