foreman 0.46.0 → 0.48.0.pre1

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 (67) 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 +51 -22
  15. data/lib/foreman/engine/cli.rb +98 -0
  16. data/lib/foreman/engine.rb +209 -148
  17. data/lib/foreman/env.rb +27 -0
  18. data/lib/foreman/export/base.rb +58 -20
  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 +1 -1
  26. data/lib/foreman/process.rb +56 -67
  27. data/lib/foreman/procfile.rb +59 -25
  28. data/lib/foreman/version.rb +1 -1
  29. data/man/foreman.1 +5 -1
  30. data/spec/foreman/cli_spec.rb +38 -152
  31. data/spec/foreman/engine_spec.rb +47 -74
  32. data/spec/foreman/export/base_spec.rb +4 -7
  33. data/spec/foreman/export/bluepill_spec.rb +7 -6
  34. data/spec/foreman/export/inittab_spec.rb +7 -7
  35. data/spec/foreman/export/launchd_spec.rb +21 -0
  36. data/spec/foreman/export/runit_spec.rb +12 -17
  37. data/spec/foreman/export/supervisord_spec.rb +7 -56
  38. data/spec/foreman/export/upstart_spec.rb +22 -21
  39. data/spec/foreman/process_spec.rb +27 -110
  40. data/spec/foreman/procfile_spec.rb +26 -16
  41. data/spec/resources/Procfile +4 -0
  42. data/spec/resources/bin/echo +2 -0
  43. data/spec/resources/bin/env +2 -0
  44. data/spec/resources/bin/test +2 -0
  45. data/spec/resources/export/bluepill/app-concurrency.pill +6 -4
  46. data/spec/resources/export/bluepill/app.pill +6 -4
  47. data/spec/resources/export/launchd/launchd-a.default +22 -0
  48. data/spec/resources/export/launchd/launchd-b.default +22 -0
  49. data/spec/resources/export/supervisord/app-alpha-1.conf +24 -0
  50. data/spec/resources/export/supervisord/app-alpha-2.conf +4 -4
  51. data/spec/spec_helper.rb +58 -6
  52. metadata +29 -28
  53. data/data/export/runit/log_run.erb +0 -7
  54. data/lib/foreman/color.rb +0 -40
  55. data/lib/foreman/procfile_entry.rb +0 -26
  56. data/lib/foreman/utils.rb +0 -18
  57. data/spec/foreman/color_spec.rb +0 -31
  58. data/spec/foreman/procfile_entry_spec.rb +0 -13
  59. data/spec/resources/export/supervisord/app-env-with-comma.conf +0 -24
  60. data/spec/resources/export/supervisord/app-env.conf +0 -21
  61. data/spec/resources/export/supervisord/app.conf +0 -24
  62. /data/spec/resources/export/runit/{app-alpha-1-log-run → app-alpha-1/log/run} +0 -0
  63. /data/spec/resources/export/runit/{app-alpha-1-run → app-alpha-1/run} +0 -0
  64. /data/spec/resources/export/runit/{app-alpha-2-log-run → app-alpha-2/log/run} +0 -0
  65. /data/spec/resources/export/runit/{app-alpha-2-run → app-alpha-2/run} +0 -0
  66. /data/spec/resources/export/runit/{app-bravo-1-log-run → app-bravo-1/log/run} +0 -0
  67. /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,37 @@
1
1
  require "foreman"
2
2
  require "foreman/helpers"
3
3
  require "foreman/engine"
4
+ require "foreman/engine/cli"
4
5
  require "foreman/export"
6
+ require "shellwords"
5
7
  require "thor"
6
- require "yaml"
7
8
 
8
9
  class Foreman::CLI < Thor
10
+
9
11
  include Foreman::Helpers
10
12
 
11
13
  class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
14
+ class_option :root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
12
15
 
13
16
  desc "start [PROCESS]", "Start the application (or a specific PROCESS)"
14
17
 
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"'
18
+ method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
19
+ method_option :formation, :type => :string, :aliases => "-m", :banner => '"alpha=5,bar=3"'
20
+ method_option :port, :type => :numeric, :aliases => "-p"
21
21
 
22
22
  class << self
23
23
  # Hackery. Take the run method away from Thor so that we can redefine it.
24
24
  def is_thor_reserved_word?(word, type)
25
- return false if word == 'run'
25
+ return false if word == "run"
26
26
  super
27
27
  end
28
28
  end
29
29
 
30
30
  def start(process=nil)
31
31
  check_procfile!
32
- engine.options[:concurrency] = "#{process}=1" if process
32
+ load_environment!
33
+ engine.load_procfile(procfile)
34
+ engine.options[:formation] = "#{process}=1" if process
33
35
  engine.start
34
36
  end
35
37
 
@@ -45,6 +47,8 @@ class Foreman::CLI < Thor
45
47
 
46
48
  def export(format, location=nil)
47
49
  check_procfile!
50
+ load_environment!
51
+ engine.load_procfile(procfile)
48
52
  formatter = Foreman::Export.formatter(format)
49
53
  formatter.new(location, engine, options).export
50
54
  rescue Foreman::Export::Exception => ex
@@ -55,16 +59,19 @@ class Foreman::CLI < Thor
55
59
 
56
60
  def check
57
61
  check_procfile!
58
- error "no processes defined" unless engine.procfile.entries.length > 0
59
- puts "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
62
+ engine.load_procfile(procfile)
63
+ error "no processes defined" unless engine.processes.length > 0
64
+ puts "valid procfile detected (#{engine.process_names.join(', ')})"
60
65
  end
61
66
 
62
- desc "run COMMAND", "Run a command using your application's environment"
67
+ desc "run COMMAND [ARGS...]", "Run a command using your application's environment"
68
+
69
+ method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
63
70
 
64
71
  def run(*args)
65
- engine.apply_environment!
72
+ load_environment!
66
73
  begin
67
- exec args.join(" ")
74
+ exec engine.env, args.shelljoin
68
75
  rescue Errno::EACCES
69
76
  error "not executable: #{args.first}"
70
77
  rescue Errno::ENOENT
@@ -72,33 +79,55 @@ class Foreman::CLI < Thor
72
79
  end
73
80
  end
74
81
 
82
+ no_tasks do
83
+ def engine
84
+ @engine ||= begin
85
+ engine_class = Foreman::Engine::CLI
86
+ engine = engine_class.new(
87
+ :formation => options[:formation],
88
+ :port => options[:port],
89
+ :root => options[:root]
90
+ )
91
+ engine
92
+ end
93
+ end
94
+ end
95
+
75
96
  private ######################################################################
76
97
 
98
+ def error(message)
99
+ puts "ERROR: #{message}"
100
+ exit 1
101
+ end
102
+
77
103
  def check_procfile!
78
104
  error("#{procfile} does not exist.") unless File.exist?(procfile)
79
105
  end
80
106
 
81
- def engine
82
- @engine ||= Foreman::Engine.new(procfile, options)
107
+ def load_environment!
108
+ if options[:env]
109
+ options[:env].split(",").each do |file|
110
+ engine.load_env file
111
+ end
112
+ else
113
+ default_env = File.join(engine.root, ".env")
114
+ engine.load_env default_env if File.exists?(default_env)
115
+ end
83
116
  end
84
117
 
85
118
  def procfile
86
119
  case
87
120
  when options[:procfile] then options[:procfile]
88
- when options[:app_root] then File.expand_path(File.join(options[:app_root], "Procfile"))
121
+ when options[:root] then File.expand_path(File.join(options[:app_root], "Procfile"))
89
122
  else "Procfile"
90
123
  end
91
124
  end
92
125
 
93
- def error(message)
94
- puts "ERROR: #{message}"
95
- exit 1
96
- end
97
-
98
126
  def options
99
127
  original_options = super
100
128
  return original_options unless File.exists?(".foreman")
101
129
  defaults = YAML::load_file(".foreman") || {}
102
130
  Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
103
131
  end
132
+
104
133
  end
@@ -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