foreman 0.47.0 → 0.48.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 (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 +57 -22
  13. data/lib/foreman/engine/cli.rb +103 -0
  14. data/lib/foreman/engine.rb +222 -149
  15. data/lib/foreman/env.rb +27 -0
  16. data/lib/foreman/export/base.rb +107 -27
  17. data/lib/foreman/export/bluepill.rb +3 -17
  18. data/lib/foreman/export/inittab.rb +8 -11
  19. data/lib/foreman/export/launchd.rb +4 -16
  20. data/lib/foreman/export/runit.rb +14 -39
  21. data/lib/foreman/export/supervisord.rb +3 -13
  22. data/lib/foreman/export/upstart.rb +9 -27
  23. data/lib/foreman/export.rb +1 -2
  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 +46 -150
  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/supervisord/app-alpha-1.conf +24 -0
  46. data/spec/resources/export/supervisord/app-alpha-2.conf +4 -4
  47. data/spec/spec_helper.rb +57 -6
  48. metadata +21 -19
  49. data/data/export/runit/log_run.erb +0 -7
  50. data/lib/foreman/color.rb +0 -40
  51. data/lib/foreman/procfile_entry.rb +0 -26
  52. data/lib/foreman/utils.rb +0 -18
  53. data/spec/foreman/color_spec.rb +0 -31
  54. data/spec/foreman/procfile_entry_spec.rb +0 -13
  55. data/spec/resources/export/supervisord/app-env-with-comma.conf +0 -24
  56. data/spec/resources/export/supervisord/app-env.conf +0 -21
  57. data/spec/resources/export/supervisord/app.conf +0 -24
  58. /data/spec/resources/export/runit/{app-alpha-1-log-run → app-alpha-1/log/run} +0 -0
  59. /data/spec/resources/export/runit/{app-alpha-1-run → app-alpha-1/run} +0 -0
  60. /data/spec/resources/export/runit/{app-alpha-2-log-run → app-alpha-2/log/run} +0 -0
  61. /data/spec/resources/export/runit/{app-alpha-2-run → app-alpha-2/run} +0 -0
  62. /data/spec/resources/export/runit/{app-bravo-1-log-run → app-bravo-1/log/run} +0 -0
  63. /data/spec/resources/export/runit/{app-bravo-1-run → app-bravo-1/run} +0 -0
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,25 +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 %>"<% engine.environment.each_pair do |var,env| %> , "<%= var.upcase %>" => "<%= env %>" <% end %>}
14
+ process.environment = <%= engine.env.merge("PORT" => port.to_s).inspect %>
15
15
  process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
16
16
  process.stop_grace_time = 45.seconds
17
17
 
18
- process.stdout = process.stderr = "<%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log"
18
+ process.stdout = process.stderr = "<%= log %>/<%= app %>-<%= name %>-<%= num %>.log"
19
19
 
20
20
  process.monitor_children do |children|
21
- children.stop_command "kill -QUIT {{PID}}"
21
+ children.stop_command "kill {{PID}}"
22
22
  end
23
23
 
24
- process.group = "<%= app %>-<%= process.name %>"
24
+ process.group = "<%= app %>-<%= name %>"
25
25
  end
26
26
  <% end %>
27
27
  <% end %>
@@ -3,7 +3,7 @@
3
3
  <plist version="1.0">
4
4
  <dict>
5
5
  <key>Label</key>
6
- <string><%= "#{app}-#{process.name}-#{num}" %></string>
6
+ <string><%= "#{app}-#{name}-#{num}" %></string>
7
7
  <key>ProgramArguments</key>
8
8
  <array>
9
9
  <string><%= process.command %></string>
@@ -13,10 +13,10 @@
13
13
  <key>RunAtLoad</key>
14
14
  <true/>
15
15
  <key>StandardErrorPath</key>
16
- <string><%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log</string>
16
+ <string><%= log %>/<%= app %>-<%= name %>-<%=num%>.log</string>
17
17
  <key>UserName</key>
18
18
  <string><%= user %></string>
19
19
  <key>WorkingDirectory</key>
20
- <string><%= engine.directory %></string>
20
+ <string><%= engine.root %></string>
21
21
  </dict>
22
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 %>=<%= shell_quote(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,36 +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"
6
+ require "foreman/version"
5
7
  require "shellwords"
6
- require "thor"
7
8
  require "yaml"
9
+ require "thor"
8
10
 
9
11
  class Foreman::CLI < Thor
12
+
10
13
  include Foreman::Helpers
11
14
 
15
+ map ["-v", "--version"] => :version
16
+
12
17
  class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
18
+ class_option :root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
13
19
 
14
20
  desc "start [PROCESS]", "Start the application (or a specific PROCESS)"
15
21
 
16
- class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
17
- class_option :app_root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
18
-
19
- method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
20
- method_option :port, :type => :numeric, :aliases => "-p"
21
- 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"
22
26
 
23
27
  class << self
24
28
  # Hackery. Take the run method away from Thor so that we can redefine it.
25
29
  def is_thor_reserved_word?(word, type)
26
- return false if word == 'run'
30
+ return false if word == "run"
27
31
  super
28
32
  end
29
33
  end
30
34
 
31
35
  def start(process=nil)
32
36
  check_procfile!
33
- engine.options[:concurrency] = "#{process}=1" if process
37
+ load_environment!
38
+ engine.load_procfile(procfile)
39
+ engine.options[:formation] = "#{process}=1" if process
34
40
  engine.start
35
41
  end
36
42
 
@@ -46,6 +52,8 @@ class Foreman::CLI < Thor
46
52
 
47
53
  def export(format, location=nil)
48
54
  check_procfile!
55
+ load_environment!
56
+ engine.load_procfile(procfile)
49
57
  formatter = Foreman::Export.formatter(format)
50
58
  formatter.new(location, engine, options).export
51
59
  rescue Foreman::Export::Exception => ex
@@ -56,16 +64,19 @@ class Foreman::CLI < Thor
56
64
 
57
65
  def check
58
66
  check_procfile!
59
- error "no processes defined" unless engine.procfile.entries.length > 0
60
- 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(', ')})"
61
70
  end
62
71
 
63
72
  desc "run COMMAND [ARGS...]", "Run a command using your application's environment"
64
73
 
74
+ method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
75
+
65
76
  def run(*args)
66
- engine.apply_environment!
77
+ load_environment!
67
78
  begin
68
- exec args.shelljoin
79
+ exec engine.env, args.shelljoin
69
80
  rescue Errno::EACCES
70
81
  error "not executable: #{args.first}"
71
82
  rescue Errno::ENOENT
@@ -73,33 +84,57 @@ class Foreman::CLI < Thor
73
84
  end
74
85
  end
75
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
+
76
103
  private ######################################################################
77
104
 
105
+ def error(message)
106
+ puts "ERROR: #{message}"
107
+ exit 1
108
+ end
109
+
78
110
  def check_procfile!
79
111
  error("#{procfile} does not exist.") unless File.exist?(procfile)
80
112
  end
81
113
 
82
- def engine
83
- @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
84
123
  end
85
124
 
86
125
  def procfile
87
126
  case
88
127
  when options[:procfile] then options[:procfile]
89
- 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"))
90
129
  else "Procfile"
91
130
  end
92
131
  end
93
132
 
94
- def error(message)
95
- puts "ERROR: #{message}"
96
- exit 1
97
- end
98
-
99
133
  def options
100
134
  original_options = super
101
135
  return original_options unless File.exists?(".foreman")
102
- defaults = YAML::load_file(".foreman") || {}
136
+ defaults = ::YAML::load_file(".foreman") || {}
103
137
  Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
104
138
  end
139
+
105
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