foreman 0.46.0 → 0.49.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 +103 -0
  16. data/lib/foreman/engine.rb +216 -148
  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 +65 -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 +26 -25
  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
data/README.md CHANGED
@@ -27,6 +27,12 @@ Manage Procfile-based applications
27
27
  * [wiki](http://github.com/ddollar/foreman/wiki)
28
28
  * [changelog](https://github.com/ddollar/foreman/blob/master/Changelog.md)
29
29
 
30
+ ## Ports
31
+
32
+ * [shoreman](https://github.com/hecticjeff/shoreman) - shell
33
+ * [honcho](https://github.com/nickstenning/honcho) - python
34
+ * [norman](https://github.com/josh/norman) - node.js
35
+
30
36
  ## Authors
31
37
 
32
38
  #### Created and maintained by
data/bin/foreman-runner CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/bin/sh
2
2
  #
3
- #/ Usage: foreman-runner [-d <dir>] <command>
3
+ #/ Usage: foreman-runner [-d <dir>] <command> [<args>...]
4
4
  #/
5
5
  #/ Run a command with exec, optionally changing directory first
6
6
 
@@ -27,10 +27,6 @@ done
27
27
 
28
28
  shift $((OPTIND-1))
29
29
 
30
- command=$1
30
+ [ -z "$1" ] && usage
31
31
 
32
- if [ -z "$1" ]; then
33
- usage
34
- fi
35
-
36
- exec $1
32
+ exec "$@"
data/bin/taskman ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $:.unshift File.expand_path("../../lib", __FILE__)
4
+
5
+ require "foreman/cli"
6
+
7
+ Foreman::CLI.engine_class = Foreman::TmuxEngine
8
+ Foreman::CLI.start
@@ -1,3 +1,4 @@
1
- ticker: ruby ./ticker $PORT
2
- error: ruby ./error
3
- utf8: ruby ./utf8
1
+ ticker: ruby ./ticker $PORT
2
+ error: ruby ./error
3
+ utf8: ruby ./utf8
4
+ spawner: ./spawner
@@ -0,0 +1,14 @@
1
+ #!/bin/sh
2
+
3
+ NAME="$1"
4
+
5
+ sigterm() {
6
+ echo "$NAME: got sigterm"
7
+ }
8
+
9
+ #trap sigterm SIGTERM
10
+
11
+ while true; do
12
+ echo "$NAME: ping $$"
13
+ sleep 1
14
+ done
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+
3
+ ./spawnee A &
4
+ ./spawnee B &
5
+ ./spawnee C &
6
+
7
+ wait
@@ -3,24 +3,25 @@ Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/
3
3
  app.uid = "<%= user %>"
4
4
  app.gid = "<%= user %>"
5
5
 
6
- <% engine.procfile.entries.each do |process| %>
7
- <% 1.upto(concurrency[process.name]) do |num| %>
8
- <% port = engine.port_for(process, num, self.port) %>
9
- app.process("<%= process.name %>-<%=num%>") do |process|
10
- process.start_command = "<%= process.command.gsub("$PORT", port.to_s) %>"
6
+ <% engine.each_process do |name, process| %>
7
+ <% 1.upto(engine.formation[name]) do |num| %>
8
+ <% port = engine.port_for(process, num) %>
9
+ app.process("<%= name %>-<%= num %>") do |process|
10
+ process.start_command = "<%= process.command %>"
11
11
 
12
- process.working_dir = "<%= engine.directory %>"
12
+ process.working_dir = "<%= engine.root %>"
13
13
  process.daemonize = true
14
- process.environment = {"PORT" => "<%= port %>"}
14
+ process.environment = <%= engine.env.merge("PORT" => port.to_s).inspect %>
15
15
  process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
16
+ process.stop_grace_time = 45.seconds
16
17
 
17
- process.stdout = process.stderr = "<%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log"
18
+ process.stdout = process.stderr = "<%= log %>/<%= app %>-<%= name %>-<%= num %>.log"
18
19
 
19
20
  process.monitor_children do |children|
20
- children.stop_command "kill -QUIT {{PID}}"
21
+ children.stop_command "kill {{PID}}"
21
22
  end
22
23
 
23
- process.group = "<%= app %>-<%= process.name %>"
24
+ process.group = "<%= app %>-<%= name %>"
24
25
  end
25
26
  <% end %>
26
27
  <% end %>
@@ -0,0 +1,22 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string><%= "#{app}-#{name}-#{num}" %></string>
7
+ <key>ProgramArguments</key>
8
+ <array>
9
+ <string><%= process.command %></string>
10
+ </array>
11
+ <key>KeepAlive</key>
12
+ <true/>
13
+ <key>RunAtLoad</key>
14
+ <true/>
15
+ <key>StandardErrorPath</key>
16
+ <string><%= log %>/<%= app %>-<%= name %>-<%=num%>.log</string>
17
+ <key>UserName</key>
18
+ <string><%= user %></string>
19
+ <key>WorkingDirectory</key>
20
+ <string><%= engine.root %></string>
21
+ </dict>
22
+ </plist>
@@ -0,0 +1,7 @@
1
+ #!/bin/sh
2
+ set -e
3
+
4
+ LOG=<%= log %>/<%= name %>-<%= num %>
5
+
6
+ test -d "$LOG" || mkdir -p m2750 "$LOG" && chown <%= user %> "$LOG"
7
+ exec chpst -u <%= user %> svlogd "$LOG"
@@ -1,3 +1,3 @@
1
1
  #!/bin/sh
2
- cd <%= engine.directory %>
3
- exec chpst -u <%= user %> -e <%= process_env_directory %> <%= process.command %>
2
+ cd <%= engine.root %>
3
+ exec chpst -u <%= user %> -e <%= File.join(location, "#{process_directory}/env") %> <%= process.command %>
@@ -1,23 +1,23 @@
1
1
  <%
2
2
  app_names = []
3
- engine.procfile.entries.each do |process|
4
- next if (conc = self.concurrency[process.name]) < 1
5
- 1.upto(self.concurrency[process.name]) do |num|
6
- port = engine.port_for(process, num, self.port)
7
- name = if (conc > 1); "#{process.name}-#{num}" else process.name; end
8
- environment = (engine.environment.keys.sort.map{ |var| %{#{var.upcase}="#{engine.environment[var]}"} } + [%{PORT="#{port}"}])
9
- app_name = "#{app}-#{name}"
10
- app_names << app_name
3
+ engine.each_process do |name, process|
4
+ 1.upto(engine.formation[name]) do |num|
5
+ port = engine.port_for(process, num)
6
+ full_name = "#{app}-#{name}-#{num}"
7
+ environment = engine.env.merge("PORT" => port.to_s).map do |key, value|
8
+ "#{key}=#{shell_quote(value)}"
9
+ end
10
+ app_names << full_name
11
11
  %>
12
- [program:<%= app_name %>]
12
+ [program:<%= full_name %>]
13
13
  command=<%= process.command %>
14
14
  autostart=true
15
15
  autorestart=true
16
16
  stopsignal=QUIT
17
- stdout_logfile=<%= log_root %>/<%=process.name%>-<%=num%>-out.log
18
- stderr_logfile=<%= log_root %>/<%=process.name%>-<%=num%>-err.log
17
+ stdout_logfile=<%= log %>/<%= name %>-<%= num %>.log
18
+ stderr_logfile=<%= log %>/<%= name %>-<%= num %>.error.log
19
19
  user=<%= user %>
20
- directory=<%= engine.directory %>
20
+ directory=<%= engine.root %>
21
21
  environment=<%= environment.join(',') %><%
22
22
  end
23
23
  end
@@ -1,8 +1,8 @@
1
1
  pre-start script
2
2
 
3
3
  bash << "EOF"
4
- mkdir -p <%= log_root %>
5
- chown -R <%= user %> <%= log_root %>
4
+ mkdir -p <%= log %>
5
+ chown -R <%= user %> <%= log %>
6
6
  EOF
7
7
 
8
8
  end script
@@ -1,5 +1,5 @@
1
- start on starting <%= app %>-<%= process.name %>
2
- stop on stopping <%= app %>-<%= process.name %>
1
+ start on starting <%= app %>-<%= name %>
2
+ stop on stopping <%= app %>-<%= name %>
3
3
  respawn
4
4
 
5
- exec su - <%= user %> -c 'cd <%= engine.directory %>; export PORT=<%= port %>;<% engine.environment.each_pair do |var,env| %> export <%= var.upcase %>=<%= env %>; <% end %> <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1'
5
+ exec su - <%= user %> -c 'cd <%= engine.root %>; export PORT=<%= port %>;<% engine.env.each_pair do |var,env| %> export <%= var.upcase %>=<%= shell_quote(env) %>; <% end %> <%= process.command %> >> <%= log %>/<%=name%>-<%=num%>.log 2>&1'
data/lib/foreman/cli.rb CHANGED
@@ -1,35 +1,42 @@
1
1
  require "foreman"
2
2
  require "foreman/helpers"
3
3
  require "foreman/engine"
4
+ require "foreman/engine/cli"
4
5
  require "foreman/export"
5
- require "thor"
6
+ require "foreman/version"
7
+ require "shellwords"
6
8
  require "yaml"
9
+ require "thor"
7
10
 
8
11
  class Foreman::CLI < Thor
12
+
9
13
  include Foreman::Helpers
10
14
 
15
+ map ["-v", "--version"] => :version
16
+
11
17
  class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
18
+ class_option :root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
12
19
 
13
20
  desc "start [PROCESS]", "Start the application (or a specific PROCESS)"
14
21
 
15
- class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
16
- class_option :app_root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
17
-
18
- method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
19
- method_option :port, :type => :numeric, :aliases => "-p"
20
- method_option :concurrency, :type => :string, :aliases => "-c", :banner => '"alpha=5,bar=3"'
22
+ method_option :color, :type => :boolean, :aliases => "-c", :desc => "Force color to be enabled"
23
+ method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
24
+ method_option :formation, :type => :string, :aliases => "-m", :banner => '"alpha=5,bar=3"'
25
+ method_option :port, :type => :numeric, :aliases => "-p"
21
26
 
22
27
  class << self
23
28
  # Hackery. Take the run method away from Thor so that we can redefine it.
24
29
  def is_thor_reserved_word?(word, type)
25
- return false if word == 'run'
30
+ return false if word == "run"
26
31
  super
27
32
  end
28
33
  end
29
34
 
30
35
  def start(process=nil)
31
36
  check_procfile!
32
- engine.options[:concurrency] = "#{process}=1" if process
37
+ load_environment!
38
+ engine.load_procfile(procfile)
39
+ engine.options[:formation] = "#{process}=1" if process
33
40
  engine.start
34
41
  end
35
42
 
@@ -45,6 +52,8 @@ class Foreman::CLI < Thor
45
52
 
46
53
  def export(format, location=nil)
47
54
  check_procfile!
55
+ load_environment!
56
+ engine.load_procfile(procfile)
48
57
  formatter = Foreman::Export.formatter(format)
49
58
  formatter.new(location, engine, options).export
50
59
  rescue Foreman::Export::Exception => ex
@@ -55,16 +64,19 @@ class Foreman::CLI < Thor
55
64
 
56
65
  def check
57
66
  check_procfile!
58
- error "no processes defined" unless engine.procfile.entries.length > 0
59
- puts "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
67
+ engine.load_procfile(procfile)
68
+ error "no processes defined" unless engine.processes.length > 0
69
+ puts "valid procfile detected (#{engine.process_names.join(', ')})"
60
70
  end
61
71
 
62
- desc "run COMMAND", "Run a command using your application's environment"
72
+ desc "run COMMAND [ARGS...]", "Run a command using your application's environment"
73
+
74
+ method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
63
75
 
64
76
  def run(*args)
65
- engine.apply_environment!
77
+ load_environment!
66
78
  begin
67
- exec args.join(" ")
79
+ exec engine.env, args.shelljoin
68
80
  rescue Errno::EACCES
69
81
  error "not executable: #{args.first}"
70
82
  rescue Errno::ENOENT
@@ -72,33 +84,57 @@ class Foreman::CLI < Thor
72
84
  end
73
85
  end
74
86
 
87
+ desc "version", "Display Foreman gem version"
88
+
89
+ def version
90
+ puts Foreman::VERSION
91
+ end
92
+
93
+ no_tasks do
94
+ def engine
95
+ @engine ||= begin
96
+ engine_class = Foreman::Engine::CLI
97
+ engine = engine_class.new(options)
98
+ engine
99
+ end
100
+ end
101
+ end
102
+
75
103
  private ######################################################################
76
104
 
105
+ def error(message)
106
+ puts "ERROR: #{message}"
107
+ exit 1
108
+ end
109
+
77
110
  def check_procfile!
78
111
  error("#{procfile} does not exist.") unless File.exist?(procfile)
79
112
  end
80
113
 
81
- def engine
82
- @engine ||= Foreman::Engine.new(procfile, options)
114
+ def load_environment!
115
+ if options[:env]
116
+ options[:env].split(",").each do |file|
117
+ engine.load_env file
118
+ end
119
+ else
120
+ default_env = File.join(engine.root, ".env")
121
+ engine.load_env default_env if File.exists?(default_env)
122
+ end
83
123
  end
84
124
 
85
125
  def procfile
86
126
  case
87
127
  when options[:procfile] then options[:procfile]
88
- when options[:app_root] then File.expand_path(File.join(options[:app_root], "Procfile"))
128
+ when options[:root] then File.expand_path(File.join(options[:app_root], "Procfile"))
89
129
  else "Procfile"
90
130
  end
91
131
  end
92
132
 
93
- def error(message)
94
- puts "ERROR: #{message}"
95
- exit 1
96
- end
97
-
98
133
  def options
99
134
  original_options = super
100
135
  return original_options unless File.exists?(".foreman")
101
- defaults = YAML::load_file(".foreman") || {}
136
+ defaults = ::YAML::load_file(".foreman") || {}
102
137
  Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
103
138
  end
139
+
104
140
  end
@@ -0,0 +1,103 @@
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, force=false)
28
+ io.extend(self)
29
+ @@color_force = force
30
+ end
31
+
32
+ def color?
33
+ return true if @@color_force
34
+ return false unless self.respond_to?(:isatty)
35
+ self.isatty && ENV["TERM"]
36
+ end
37
+
38
+ def color(name)
39
+ return "" unless color?
40
+ return "" unless ansi = ANSI[name.to_sym]
41
+ "\e[#{ansi}m"
42
+ end
43
+
44
+ end
45
+
46
+ FOREMAN_COLORS = %w( cyan yellow green magenta red blue intense_cyan intense_yellow
47
+ intense_green intense_magenta intense_red, intense_blue )
48
+
49
+ def startup
50
+ @colors = map_colors
51
+ proctitle "foreman: master"
52
+ end
53
+
54
+ def output(name, data)
55
+ data.to_s.chomp.split("\n").each do |message|
56
+ Color.enable($stdout, options[:color]) unless $stdout.respond_to?(:color?)
57
+ output = ""
58
+ output += $stdout.color(@colors[name.split(".").first].to_sym)
59
+ output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
60
+ output += $stdout.color(:reset)
61
+ output += message
62
+ $stdout.puts output
63
+ $stdout.flush
64
+ end
65
+ rescue Errno::EPIPE
66
+ terminate_gracefully
67
+ end
68
+
69
+ def shutdown
70
+ end
71
+
72
+ private
73
+
74
+ def name_padding
75
+ @name_padding ||= begin
76
+ index_padding = @names.values.map { |n| formation[n] }.max.to_s.length + 1
77
+ name_padding = @names.values.map { |n| n.length + index_padding }.sort.last
78
+ [ 6, name_padding ].max
79
+ end
80
+ end
81
+
82
+ def pad_process_name(name)
83
+ name.ljust(name_padding, " ")
84
+ end
85
+
86
+ def map_colors
87
+ colors = Hash.new("white")
88
+ @names.values.each_with_index do |name, index|
89
+ colors[name] = FOREMAN_COLORS[index % FOREMAN_COLORS.length]
90
+ end
91
+ colors["system"] = "intense_white"
92
+ colors
93
+ end
94
+
95
+ def proctitle(title)
96
+ $0 = title
97
+ end
98
+
99
+ def termtitle(title)
100
+ printf("\033]0;#{title}\007") unless Foreman.windows?
101
+ end
102
+
103
+ end