Compare commits

..

53 Commits

Author SHA1 Message Date
199e7de97c update gems. 2014-03-17 20:12:11 +00:00
dlage
defc40b911 Merge pull request #5 from phemmer/systemd-dependencies
export: fix systemd dependencies
2014-03-17 19:39:14 +00:00
dlage
37dcba0e33 Merge pull request #3 from phemmer/shellfix
Shellfix
2014-03-17 19:37:20 +00:00
dlage
d205e3ba2e Merge pull request #4 from phemmer/run-nofork
don't fork on 'run'
2014-03-17 19:37:05 +00:00
dlage
9905334f01 Merge pull request #2 from ged/better-357-fix
A better fix for #357.
2014-03-17 19:29:45 +00:00
dlage
dd292ed7da Merge pull request #1 from petergoldstein/feature/add_2_1_0
Feature/add 2 1 0
2014-03-17 19:25:46 +00:00
Patrick Hemmer
67ffbe2aa2 export: fix systemd dependencies
With the previous way systemd services were exported, you had to enable every single process one by one to get the service operational.

Consider the following Procfile:

    web: bundle exec thin start
    worker: bundle exec worker start

Exported with:

    foreman export systemd /usr/lib/systemd/system -a myapp -c worker=3

You will have to perform the following to start the service:

    systemctl enable myapp-web-1.service
    systemctl enable myapp-worker-1.service
    systemctl enable myapp-worker-2.service
    systemctl enable myapp-worker-3.service
    systemctl enable myapp-web.target
    systemctl enable myapp-worker.target
    systemctl start myapp.target

Thats 7 commands, and you have to know the names of each service. Nasty.

With the changes here, you will have to perform the following:

    systemctl enable myapp.target
    systemctl start myapp.target

You can also `systemctl start myapp.target` without enabling as well. Previously if you tried to start `myapp.target` without enabling each individual process it would not work.

---

Additionally, this change also adds the dependency to 'multi-user.target' (the default behavior for all services). This will properly shut down (or start) the app when the system changes runlevels. The previous method did not do this.
2014-02-17 00:40:39 -05:00
Peter M. Goldstein
da8bba5941 Use a 1.9.2 compatible version of rdiscount 2014-01-13 17:38:11 -08:00
Peter M. Goldstein
2aa1cbf94f Bump up gem versions in Gemfile.lock. Minor changes to eliminate deprecation warnings. 2014-01-13 17:30:58 -08:00
Peter M. Goldstein
34a1163fe9 Add Ruby 2.1.0, clean up references in allow_failures to now un-tested rvms. 2014-01-13 15:28:47 -08:00
Patrick Hemmer
2e5f610b76 don't fork on 'run'
This removes the fork when doing `foreman run`. The fork results in an unnecessary process laying around. The parent process doesn't do anything other than fork and wait for the child to exit.
2013-11-26 00:09:19 -05:00
Michael Granger
20392d98a1 A better fix for #357.
This is a better fix for several reasons:

- It's more portable; the arguments/options to 'ps' vary widely across
  platforms.
- Killing children by forking yet more children (i.e., via backtick) is
  problematic when you're trying to kill processes because of resource
  starvation.
- Processes should not signal processes other than their own children,
  especially in a generic task-runner like Foreman. If the signal
  should propagate, then the sub-process should propagate signals to its
  children itself. There's no way to know what cleanup or preparation
  is necessary for a clean shutdown (e.g., SIGINT/SIGTERM), and
  in what order grandchild processes should be shut down to properly
  release locks, close files, etc.
- foreman-runner doesn't (shouldn't?) create grandchild processes; the
  last thing it does is `exec` the program it's launching, which
  will replace the program of the *current* process with the one
  being started up.
2013-11-05 11:56:13 -08:00
Patrick Hemmer
e8538d9f45 exec on upstart to prevent useless procs floating around 2013-10-02 20:41:51 -04:00
Patrick Hemmer
4a51a2aa8a specify a shell when using upstart 2013-10-02 20:08:47 -04:00
David Dollar
1905a7c310 Merge pull request #388 from brixen/patch-1
Update README.md
2013-09-04 16:49:36 -07:00
Brian Shirai
134b31f599 Update README.md 2013-09-04 16:47:09 -07:00
David Dollar
2b97cb458a Merge pull request #366 from wfarr/tgz-install-foreman-to-bin
Install tgz/foreman to bin/foreman in tgz package
2013-05-27 14:06:42 -07:00
Will Farrington
03d76d4254 Install tgz/foreman to bin/foreman in tgz package 2013-05-26 07:49:18 -07:00
David Dollar
133228f247 Merge pull request #305 from marclennox/master
Add start-stop-daemon export support with upstart
2013-05-03 13:27:54 -07:00
David Dollar
bfba2cad71 Merge pull request #360 from kjwierenga/feature/require-posix-spawn
Feature/require posix spawn
2013-05-03 12:55:16 -07:00
Klaas Jan Wierenga
e245026f65 Fail with an error on Ruby 1.8 when posix-spawn is not present. 2013-05-03 21:44:57 +02:00
Klaas Jan Wierenga
ffc73366b2 Require and use 'posix/spawn' when running ruby 1.8 without using Foreman.ruby_18? (which is the subject under test). 2013-05-03 21:29:17 +02:00
Marc Lennox
284503899a Added start-stop-daemon support. 2013-05-03 12:54:08 -04:00
David Dollar
144be02bbd Merge pull request #353 from austiniam/master
added support for directories with single quote
2013-05-03 07:29:17 -07:00
Austin Cherry
9734a2ed65 fixed conflicts 2013-05-03 07:25:56 -07:00
Austin
0fff148fe0 modified to use shellescape instead of gsub 2013-05-03 07:23:25 -07:00
Austin
d19a9aa043 added support for directories with single quotes. fixes #315 2013-05-03 07:22:16 -07:00
David Dollar
c3abaad353 kill the children, not self 2013-05-03 09:54:20 -04:00
David Dollar
b1f91d4505 fix docs, thanks to @Invizory 2013-05-03 09:49:31 -04:00
David Dollar
0c7b8ddd79 not sure how this snuck in, not in the exporter format 2013-05-03 09:44:25 -04:00
David Dollar
189b751d9f update docs 2013-05-03 09:44:25 -04:00
David Dollar
619bd03bb8 Merge pull request #340 from ldmosquera/set_env_var_with_process_name
Set FOREMAN_PROCESS_NAME env var for spawned procs
2013-05-03 06:42:04 -07:00
David Dollar
fd836b46c0 Merge pull request #355 from JoeyButler/fix_typo_in_man
Fix typo in manual page.
2013-05-03 06:38:42 -07:00
David Dollar
2379259b33 Merge pull request #359 from kjwierenga/feature/make-ruby-18-compatible
Fix specs that were broken on ruby 1.8. Use POSIX::Spawn.spawn to make foreman robust on ruby 1.8.7.
2013-05-03 06:32:01 -07:00
Klaas Jan Wierenga
baf842cdd4 The wrapped_command has spaces which triggers Ruby to fork a system shell (with /bin/sh -c). This causes orphaned processes on some systems (i.e. Linux). Fix this by splitting the command using String#shellsplit and using ruby's splat operator (*) to pass discrete arguments to spawn. 2013-05-02 16:30:39 +02:00
Klaas Jan Wierenga
5c06aaaa57 Enable ruby 1.8.7 building on Travis. All specs should pass now on ruby 1.8.7. 2013-05-02 12:49:05 +02:00
Klaas Jan Wierenga
75b782b664 Use POSIX::Spawn to make foreman ruby 1.8 compatible and have all specs passing. 2013-05-02 12:39:23 +02:00
Klaas Jan Wierenga
9b4bd10cdb Use pipe factory method to handle 1.8 incompatible signature for IO.pipe. 2013-05-02 12:37:06 +02:00
Klaas Jan Wierenga
fff82dc685 Ruby 1.8 doesn't have the concept of string encodings. Compare to the string as is. 2013-05-02 11:00:47 +02:00
David Dollar
da0a9f2de3 Merge pull request #294 from bfulton/master
add systemd export
2013-04-22 20:37:39 -07:00
Bright Fulton
7d77d8ff1a updated systemd export spec after rebasing included 5ef8bbdb 2013-04-20 13:48:27 -04:00
Joey Butler
51e181da29 Fix typo in manual page. 2013-04-17 10:47:03 -07:00
Leonardo Mosquera
9866a341ca Add unit test for PS env var 2013-04-16 22:31:34 -03:00
Leonardo Mosquera
90848e7dea Change FOREMAN_PROCESS_NAME to just PS 2013-04-16 22:31:15 -03:00
David Dollar
a92e24b17c changelog 2013-04-15 15:35:21 -04:00
Bright Fulton
669a920c1e fix spec after d4ab495 2013-04-15 14:43:02 -04:00
Bright Fulton
d357197718 better default for things which intentionally daemonize child processes, the default KillMode is control-group which survives daemonization 2013-04-15 14:43:02 -04:00
bfulton
f6b57d7b92 rough draft for systemd export support
http://0pointer.de/blog/projects/systemd.html

This adds support for exporting systemd targets and services.  The
structure is based on the existing upstart support.

Quality is draft and expected to refine in the coming weeks.

One Foremanism that is not respected by these export templates is the
usual log output location, instead stdout and stderr go to syslog.
2013-04-15 14:43:02 -04:00
Austin
e79588fd40 modified to use shellescape instead of gsub 2013-04-15 08:01:24 -07:00
Austin
6611d818b1 third time is the charm. :) 2013-04-11 09:14:01 -07:00
Austin
ad4d59ae14 fixes #315 2013-04-11 08:49:36 -07:00
Austin
434f30fe42 added support for directories with single quotes. fixes #315 2013-04-11 08:44:16 -07:00
Leonardo Mosquera
95a1d49e9d Set FOREMAN_PROCESS_NAME env var for spawned procs
This way, processes can identify themselves to metrics logging systems.
2013-03-14 21:34:39 -03:00
42 changed files with 600 additions and 150 deletions

View File

@@ -2,10 +2,7 @@ script: bundle exec rake spec
matrix:
allow_failures:
- rvm: 1.8.7
- rvm: jruby
- rvm: rbx
- rvm: ree
notifications:
email: false
@@ -16,7 +13,9 @@ notifications:
- http://dx-helper.herokuapp.com/travis
rvm:
- 1.8.7
- 1.9.2
- 1.9.3
- 2.0.0
- 2.1.0
- jruby

View File

@@ -1,3 +1,17 @@
## 0.63.0 (2013-04-15)
* Revert "Ensure foreman is the process group leader" [John Griffin]
* remove posix-spawn dependency as it does not work in jruby 1.7.3 [Andrew Brown & Corey Downing]
* Replace Foreman::Env with dotenv [Brandon Keepers]
* [foreman-runner] fix sourcing as . is rarely in PATH [Barry Allard]
* Fixed specs to pass. [Kentaro Kuribayashi]
* Permit underscore for command name in Procfile. [Kentaro Kuribayashi]
* Update man/foreman.1 [Patrick Ellis]
* Remove tmux option from man page [Donald Plummer]
* Prevent upstart export from deleting similarly named upstart files [Andy Morris]
* Add MIT license text [Per Andersson]
* use "start|stop\ on runlevel [x]" for upstart config [Nick Messick]
## 0.62.0 (2013-03-08)
* Merge pull request #334 from ged/reentrant_signal_handlers [David Dollar]

View File

@@ -6,7 +6,7 @@ platform :mingw do
gem "win32console", "~> 1.3.0"
end
platform :jruby do
platform :jruby, :ruby_18 do
gem "posix-spawn", "~> 0.3.6"
end
@@ -18,6 +18,8 @@ group :development do
gem 'rr', '~> 1.0.2'
gem 'rspec', '~> 2.0'
gem "simplecov", :require => false
gem 'timecop'
gem 'timecop', '0.6.1'
gem 'yard'
gem 'mime-types', '~> 1.25.1'
gem 'rdiscount', '~> 1.6.8'
end

View File

@@ -8,44 +8,46 @@ PATH
GEM
remote: http://rubygems.org/
specs:
aws-s3 (0.6.2)
aws-s3 (0.6.3)
builder
mime-types
xml-simple
builder (3.0.0)
diff-lcs (1.1.3)
dotenv (0.7.0)
builder (3.2.2)
diff-lcs (1.2.5)
docile (1.1.3)
dotenv (0.10.0)
fakefs (0.3.2)
hpricot (0.8.6)
hpricot (0.8.6-java)
mime-types (1.16)
multi_json (1.0.4)
mustache (0.11.2)
posix-spawn (0.3.6)
rake (0.9.2.2)
rdiscount (1.6.5)
mime-types (1.25.1)
multi_json (1.9.0)
mustache (0.99.5)
posix-spawn (0.3.8)
rake (10.1.1)
rdiscount (1.6.8)
ronn (0.7.3)
hpricot (>= 0.8.2)
mustache (>= 0.7.0)
rdiscount (>= 1.5.8)
rr (1.0.2)
rspec (2.8.0)
rspec-core (~> 2.8.0)
rspec-expectations (~> 2.8.0)
rspec-mocks (~> 2.8.0)
rspec-core (2.8.0)
rspec-expectations (2.8.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.8.0)
simplecov (0.5.4)
multi_json (~> 1.0.3)
simplecov-html (~> 0.5.3)
simplecov-html (0.5.3)
thor (0.16.0)
timecop (0.3.5)
win32console (1.3.0-x86-mingw32)
xml-simple (1.0.15)
yard (0.8.2)
rr (1.0.5)
rspec (2.14.1)
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
rspec-core (2.14.8)
rspec-expectations (2.14.5)
diff-lcs (>= 1.1.3, < 2.0)
rspec-mocks (2.14.6)
simplecov (0.8.2)
docile (~> 1.1.0)
multi_json
simplecov-html (~> 0.8.0)
simplecov-html (0.8.0)
thor (0.18.1)
timecop (0.6.1)
win32console (1.3.2-x86-mingw32)
xml-simple (1.1.3)
yard (0.8.7.3)
PLATFORMS
java
@@ -56,12 +58,14 @@ DEPENDENCIES
aws-s3
fakefs (~> 0.3.2)
foreman!
mime-types (~> 1.25.1)
posix-spawn (~> 0.3.6)
rake
rdiscount (~> 1.6.8)
ronn
rr (~> 1.0.2)
rspec (~> 2.0)
simplecov
timecop
timecop (= 0.6.1)
win32console (~> 1.3.0)
yard

View File

@@ -32,6 +32,7 @@ Manage Procfile-based applications
* [shoreman](https://github.com/hecticjeff/shoreman) - shell
* [honcho](https://github.com/nickstenning/honcho) - python
* [norman](https://github.com/josh/norman) - node.js
* [forego](https://github.com/ddollar/forego) - Go
## Authors

View File

@@ -0,0 +1,14 @@
pre-start script
bash << "EOF"
mkdir -p <%= log %>
chown -R <%= user %> <%= log %>
mkdir -p <%= run %>
chown -R <%= user %> <%= run %>
EOF
end script
start on runlevel [2345]
stop on runlevel [016]

View File

@@ -0,0 +1,8 @@
start on starting <%= app %>-<%= name.gsub('_', '-') %>
stop on stopping <%= app %>-<%= name.gsub('_', '-') %>
respawn
env PORT=<%= port %><% engine.env.each_pair do |var, env| %>
env <%= var.upcase %>=<%= env %><% end %>
exec start-stop-daemon --start --chuid <%= user %> --chdir <%= engine.root %> --make-pidfile --pidfile <%= run %>/<%= app %>-<%= name %>-<%= num %>.pid --exec <%= executable %><%= arguments %> >> <%= log %>/<%= app %>-<%= name %>-<%= num %>.log 2>&1

View File

@@ -0,0 +1,2 @@
start on starting <%= app %>
stop on stopping <%= app %>

View File

@@ -0,0 +1,6 @@
[Unit]
StopWhenUnneeded=true
Wants=<%= process_master_names.join(' ') %>
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,15 @@
[Unit]
StopWhenUnneeded=true
[Service]
User=<%= user %>
WorkingDirectory=<%= engine.root %>
Environment=PORT=<%= port %><% engine.env.each_pair do |var,env| %>
Environment=<%= var.upcase %>=<%= env %><% end %>
ExecStart=/bin/bash -lc '<%= process.command %>'
Restart=always
StandardInput=null
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n
KillMode=process

View File

@@ -0,0 +1,3 @@
[Unit]
StopWhenUnneeded=true
Wants=<%= process_names.join(' ') %>

View File

@@ -2,4 +2,4 @@ start on starting <%= app %>-<%= name %>
stop on stopping <%= app %>-<%= name %>
respawn
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'
exec su - <%= user %> -s /bin/sh -c 'cd <%= engine.root %>; export PORT=<%= port %>;<% engine.env.each_pair do |var,env| %> export <%= var.upcase %>=<%= shell_quote(env) %>; <% end %> exec <%= process.command %> >> <%= log %>/<%=name%>-<%=num%>.log 2>&1'

2
dist/tgz.rake vendored
View File

@@ -4,7 +4,7 @@ file pkg("foreman-#{version}.tgz") => distribution_files do |t|
assemble_distribution
assemble_gems
rm_f "bin/foreman"
assemble resource("tgz/foreman"), "foreman", 0755
assemble resource("tgz/foreman"), "bin/foreman", 0755
end
sh "tar czvf #{t.name} foreman"

View File

@@ -1,54 +0,0 @@
if defined?(Capistrano)
Capistrano::Configuration.instance(:must_exist).load do
namespace :foreman do
desc <<-DESC
Export the Procfile to upstart. Will use sudo if available.
You can override any of these defaults by setting the variables shown below.
set :foreman_format, "upstart"
set :foreman_location, "/etc/init"
set :foreman_procfile, "Procfile"
set :foreman_app, application
set :foreman_user, user
set :foreman_log, 'shared_path/log'
set :foreman_concurrency, false
DESC
task :export, :roles => :app do
bundle_cmd = fetch(:bundle_cmd, "bundle")
foreman_format = fetch(:foreman_format, "upstart")
foreman_location = fetch(:foreman_location, "/etc/init")
foreman_procfile = fetch(:foreman_procfile, "Procfile")
foreman_app = fetch(:foreman_app, application)
foreman_user = fetch(:foreman_user, user)
foreman_log = fetch(:foreman_log, "#{shared_path}/log")
foreman_concurrency = fetch(:foreman_concurrency, false)
args = ["#{foreman_format} #{foreman_location}"]
args << "-f #{foreman_procfile}"
args << "-a #{foreman_app}"
args << "-u #{foreman_user}"
args << "-l #{foreman_log}"
args << "-c #{foreman_concurrency}" if foreman_concurrency
run "cd #{release_path} && #{sudo} #{bundle_cmd} exec foreman export #{args.join(' ')}"
end
desc "Start the application services"
task :start, :roles => :app do
run "#{sudo} start #{application}"
end
desc "Stop the application services"
task :stop, :roles => :app do
run "#{sudo} stop #{application}"
end
desc "Restart the application services"
task :restart, :roles => :app do
run "#{sudo} start #{application} || #{sudo} restart #{application}"
end
end
end
end

View File

@@ -34,6 +34,7 @@ class Foreman::CLI < Thor
end
def start(process=nil)
require_posix_spawn_for_ruby_18!
check_procfile!
load_environment!
engine.load_procfile(procfile)
@@ -45,6 +46,7 @@ class Foreman::CLI < Thor
method_option :app, :type => :string, :aliases => "-a"
method_option :log, :type => :string, :aliases => "-l"
method_option :run, :type => :string, :aliases => "-r", :desc => "Specify the pid file directory, defaults to /var/run/<application>"
method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
method_option :port, :type => :numeric, :aliases => "-p"
method_option :user, :type => :string, :aliases => "-u"
@@ -81,22 +83,18 @@ class Foreman::CLI < Thor
engine.load_procfile(procfile)
end
pid = fork do
begin
engine.env.each { |k,v| ENV[k] = v }
if args.size == 1 && process = engine.process(args.first)
process.exec(:env => engine.env)
else
exec args.shelljoin
end
rescue Errno::EACCES
error "not executable: #{args.first}"
rescue Errno::ENOENT
error "command not found: #{args.first}"
begin
engine.env.each { |k,v| ENV[k] = v }
if args.size == 1 && process = engine.process(args.first)
process.exec(:env => engine.env)
else
exec args.shelljoin
end
rescue Errno::EACCES
error "not executable: #{args.first}"
rescue Errno::ENOENT
error "command not found: #{args.first}"
end
Process.wait(pid)
exit $?.exitstatus
end
desc "version", "Display Foreman gem version"
@@ -137,6 +135,14 @@ private ######################################################################
end
end
def require_posix_spawn_for_ruby_18!
begin
Kernel.require 'posix/spawn' # Use Kernel explicitly so we can mock the require call in the spec
rescue LoadError
error "foreman requires gem `posix-spawn` on Ruby #{RUBY_VERSION}. Please `gem install posix-spawn`."
end if Foreman.ruby_18?
end
def procfile
case
when options[:procfile] then options[:procfile]

View File

@@ -176,19 +176,14 @@ class Foreman::Engine
#
# @param [String] signal The signal to send to each process
#
def kill_children(signal="SIGTERM")
if Foreman.windows?
@running.each do |pid, (process, index)|
system "sending #{signal} to #{name_for(pid)} at pid #{pid}"
begin
Process.kill(signal, pid)
rescue Errno::ESRCH, Errno::EPERM
end
end
else
def kill_children( signal="SIGTERM" )
@running.each do |pid, (process, index)|
system "sending #{signal} to #{name_for(pid)} at pid #{pid}"
begin
Process.kill signal, *@running.keys unless @running.empty?
rescue Errno::ESRCH, Errno::EPERM
Process.kill( signal, pid )
rescue Errno::ESRCH, Errno::EPERM => err
system " %p when sending signal %p to pid %d: %s" %
[ err.class, signal, pid, err.message ]
end
end
end
@@ -198,14 +193,7 @@ class Foreman::Engine
# @param [String] signal The signal to send
#
def killall(signal="SIGTERM")
if Foreman.windows?
kill_children(signal)
else
begin
Process.kill "-#{signal}", Process.pid
rescue Errno::ESRCH, Errno::EPERM
end
end
kill_children(signal)
end
# Get the process formation
@@ -302,6 +290,10 @@ private
def name_for(pid)
process, index = @running[pid]
name_for_index(process, index)
end
def name_for_index(process, index)
[ @names[process], index.to_s ].compact.join(".")
end
@@ -350,7 +342,8 @@ private
reader, writer = create_pipe
begin
pid = process.run(:output => writer, :env => {
"PORT" => port_for(process, n).to_s
"PORT" => port_for(process, n).to_s,
"PS" => name_for_index(process, n)
})
writer.puts "started with pid #{pid}"
rescue Errno::ENOENT
@@ -422,7 +415,7 @@ private
end
rescue Timeout::Error
system "sending SIGKILL to all processes"
killall "SIGKILL"
kill_children "SIGKILL"
end
end

View File

@@ -28,7 +28,9 @@ end
require "foreman/export/base"
require "foreman/export/inittab"
require "foreman/export/upstart"
require "foreman/export/daemon"
require "foreman/export/bluepill"
require "foreman/export/runit"
require "foreman/export/supervisord"
require "foreman/export/launchd"
require "foreman/export/systemd"

View File

@@ -47,7 +47,9 @@ class Foreman::Export::Base
error("Must specify a location") unless location
FileUtils.mkdir_p(location) rescue error("Could not create: #{location}")
FileUtils.mkdir_p(log) rescue error("Could not create: #{log}")
FileUtils.mkdir_p(run) rescue error("Could not create: #{run}")
FileUtils.chown(user, nil, log) rescue error("Could not chown #{log} to #{user}")
FileUtils.chown(user, nil, run) rescue error("Could not chown #{run} to #{user}")
end
def app
@@ -58,6 +60,10 @@ class Foreman::Export::Base
options[:log] || "/var/log/#{app}"
end
def run
options[:run] || "/var/run/#{app}"
end
def user
options[:user] || app
end

View File

@@ -0,0 +1,28 @@
require "erb"
require "foreman/export"
class Foreman::Export::Daemon < Foreman::Export::Base
def export
super
(Dir["#{location}/#{app}-*.conf"] << "#{location}/#{app}.conf").each do |file|
clean file
end
write_template "daemon/master.conf.erb", "#{app}.conf", binding
engine.each_process do |name, process|
next if engine.formation[name] < 1
write_template "daemon/process_master.conf.erb", "#{app}-#{name}.conf", binding
1.upto(engine.formation[name]) do |num|
port = engine.port_for(process, num)
arguments = process.command.split(" ")
executable = arguments.slice!(0)
arguments = arguments.size > 0 ? " -- #{arguments.join(' ')}" : ""
write_template "daemon/process.conf.erb", "#{app}-#{name}-#{num}.conf", binding
end
end
end
end

View File

@@ -0,0 +1,32 @@
require "erb"
require "foreman/export"
class Foreman::Export::Systemd < Foreman::Export::Base
def export
super
Dir["#{location}/#{app}*.target"].concat(Dir["#{location}/#{app}*.service"]).each do |file|
clean file
end
process_master_names = []
engine.each_process do |name, process|
next if engine.formation[name] < 1
process_names = []
1.upto(engine.formation[name]) do |num|
port = engine.port_for(process, num)
write_template "systemd/process.service.erb", "#{app}-#{name}-#{num}.service", binding
process_names << "#{app}-#{name}-#{num}.service"
end
write_template "systemd/process_master.target.erb", "#{app}-#{name}.target", binding
process_master_names << "#{app}-#{name}.target"
end
write_template "systemd/master.target.erb", "#{app}.target", binding
end
end

View File

@@ -1,4 +1,5 @@
require "foreman"
require "shellwords"
class Foreman::Process
@@ -47,25 +48,18 @@ class Foreman::Process
def run(options={})
env = @options[:env].merge(options[:env] || {})
output = options[:output] || $stdout
runner = "#{Foreman.runner}".shellescape
if Foreman.windows?
Dir.chdir(cwd) do
Process.spawn env, expanded_command(env), :out => output, :err => output
end
elsif Foreman.jruby_18?
elsif Foreman.jruby_18? || Foreman.ruby_18?
require "posix/spawn"
wrapped_command = "#{Foreman.runner} -d '#{cwd}' -p -- #{command}"
POSIX::Spawn.spawn env, wrapped_command, :out => output, :err => output
elsif Foreman.ruby_18?
fork do
$stdout.reopen output
$stderr.reopen output
env.each { |k,v| ENV[k] = v }
wrapped_command = "#{Foreman.runner} -d '#{cwd}' -p -- #{command}"
Kernel.exec wrapped_command
end
wrapped_command = "#{runner} -d '#{cwd.shellescape}' -p -- #{expanded_command(env)}"
POSIX::Spawn.spawn(*spawn_args(env, wrapped_command.shellsplit, {:out => output, :err => output}))
else
wrapped_command = "#{Foreman.runner} -d '#{cwd}' -p -- #{command}"
wrapped_command = "#{runner} -d '#{cwd.shellescape}' -p -- #{command}"
Process.spawn env, wrapped_command, :out => output, :err => output
end
end
@@ -122,4 +116,14 @@ class Foreman::Process
File.expand_path(@options[:cwd] || ".")
end
private
def spawn_args(env, argv, options)
args = []
args << env
args += argv
args << options
args
end
end

View File

@@ -1,7 +1,7 @@
.\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "FOREMAN" "1" "January 2013" "Foreman 0.61.0" "Foreman Manual"
.TH "FOREMAN" "1" "May 2013" "Foreman 0.63.0" "Foreman Manual"
.
.SH "NAME"
\fBforeman\fR \- manage Procfile\-based applications
@@ -107,9 +107,18 @@ bluepill
inittab
.
.IP "\(bu" 4
launchd
.
.IP "\(bu" 4
runit
.
.IP "\(bu" 4
supervisord
.
.IP "\(bu" 4
systemd
.
.IP "\(bu" 4
upstart
.
.IP "" 0
@@ -130,6 +139,18 @@ EX02:4:respawn:/bin/su \- example \-c \'PORT=5100 bundle exec rake jobs:work >>
.
.IP "" 0
.
.SH "SYSTEMD EXPORT"
Will create a series of systemd scripts in the location you specify\. Scripts will be structured to make the following commands valid:
.
.P
\fBsystemctl start appname\.target\fR
.
.P
\fBsystemctl stop appname\-processname\.target\fR
.
.P
\fBsystemctl restart appname\-processname\-3\.service\fR
.
.SH "UPSTART EXPORT"
Will create a series of upstart scripts in the location you specify\. Scripts will be structured to make the following commands valid:
.
@@ -157,7 +178,7 @@ job: bundle exec rake jobs:work
.IP "" 0
.
.P
A process name may contain letters, numbers amd the underscore character\. You can validate your Procfile format using the \fBcheck\fR command:
A process name may contain letters, numbers and the underscore character\. You can validate your Procfile format using the \fBcheck\fR command:
.
.IP "" 4
.

View File

@@ -101,8 +101,14 @@ foreman currently supports the following output formats:
* inittab
* launchd
* runit
* supervisord
* systemd
* upstart
## INITTAB EXPORT
@@ -114,6 +120,17 @@ Will export a chunk of inittab-compatible configuration:
EX02:4:respawn:/bin/su - example -c 'PORT=5100 bundle exec rake jobs:work >> /var/log/job-1.log 2>&1'
# ----- end foreman example processes -----
## SYSTEMD EXPORT
Will create a series of systemd scripts in the location you specify. Scripts
will be structured to make the following commands valid:
`systemctl start appname.target`
`systemctl stop appname-processname.target`
`systemctl restart appname-processname-3.service`
## UPSTART EXPORT
Will create a series of upstart scripts in the location you specify. Scripts
@@ -133,7 +150,7 @@ to run it.
web: bundle exec thin start
job: bundle exec rake jobs:work
A process name may contain letters, numbers amd the underscore character.
A process name may contain letters, numbers and the underscore character.
You can validate your Procfile format using the `check` command:
$ foreman check
@@ -168,7 +185,7 @@ Export the application in upstart format:
Run one process type from the application defined in a specific Procfile:
$ foreman start alpha -p ~/myapp/Procfile
$ foreman start alpha -f ~/myapp/Procfile
## COPYRIGHT

View File

@@ -44,6 +44,13 @@ describe "Foreman::CLI", :fakefs do
output.should =~ /test.1 \| testing/
end
end
it "sets PS variable with the process name" do
without_fakefs do
output = foreman("start -f #{resource_path("Procfile")}")
output.should =~ /ps.1 \| PS env var is ps.1/
end
end
end
end
@@ -93,4 +100,12 @@ describe "Foreman::CLI", :fakefs do
end
end
describe "when posix-spawn is not present on ruby 1.8" do
it "should fail with an error" do
mock(Kernel).require('posix/spawn') { raise LoadError }
output = foreman("start -f #{resource_path("Procfile")}")
output.should == "ERROR: foreman requires gem `posix-spawn` on Ruby #{RUBY_VERSION}. Please `gem install posix-spawn`.\n"
end
end if running_ruby_18?
end

View File

@@ -0,0 +1,97 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export/daemon"
require "tmpdir"
describe Foreman::Export::Daemon, :fakefs do
let(:procfile) { write_procfile("/tmp/app/Procfile") }
let(:formation) { nil }
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
let(:options) { Hash.new }
let(:daemon) { Foreman::Export::Daemon.new("/tmp/init", engine, options) }
before(:each) { load_export_templates_into_fakefs("daemon") }
before(:each) { stub(daemon).say }
it "exports to the filesystem" do
daemon.export
File.read("/tmp/init/app.conf").should == example_export_file("daemon/app.conf")
File.read("/tmp/init/app-alpha.conf").should == example_export_file("daemon/app-alpha.conf")
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("daemon/app-alpha-1.conf")
File.read("/tmp/init/app-bravo.conf").should == example_export_file("daemon/app-bravo.conf")
File.read("/tmp/init/app-bravo-1.conf").should == example_export_file("daemon/app-bravo-1.conf")
end
it "cleans up if exporting into an existing dir" do
mock(FileUtils).rm("/tmp/init/app.conf")
mock(FileUtils).rm("/tmp/init/app-alpha.conf")
mock(FileUtils).rm("/tmp/init/app-alpha-1.conf")
mock(FileUtils).rm("/tmp/init/app-bravo.conf")
mock(FileUtils).rm("/tmp/init/app-bravo-1.conf")
mock(FileUtils).rm("/tmp/init/app-foo-bar.conf")
mock(FileUtils).rm("/tmp/init/app-foo-bar-1.conf")
mock(FileUtils).rm("/tmp/init/app-foo_bar.conf")
mock(FileUtils).rm("/tmp/init/app-foo_bar-1.conf")
daemon.export
daemon.export
end
it "does not delete exported files for similarly named applications" do
FileUtils.mkdir_p "/tmp/init"
["app2", "app2-alpha", "app2-alpha-1"].each do |name|
path = "/tmp/init/#{name}.conf"
FileUtils.touch(path)
dont_allow(FileUtils).rm(path)
end
daemon.export
end
context "with a formation" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
daemon.export
File.read("/tmp/init/app.conf").should == example_export_file("daemon/app.conf")
File.read("/tmp/init/app-alpha.conf").should == example_export_file("daemon/app-alpha.conf")
File.read("/tmp/init/app-alpha-1.conf").should == example_export_file("daemon/app-alpha-1.conf")
File.read("/tmp/init/app-alpha-2.conf").should == example_export_file("daemon/app-alpha-2.conf")
File.exists?("/tmp/init/app-bravo-1.conf").should == false
end
end
context "with alternate templates" do
let(:template) { "/tmp/alternate" }
let(:options) { { :app => "app", :template => template } }
before do
FileUtils.mkdir_p template
File.open("#{template}/master.conf.erb", "w") { |f| f.puts "alternate_template" }
end
it "can export with alternate template files" do
daemon.export
File.read("/tmp/init/app.conf").should == "alternate_template\n"
end
end
context "with alternate templates from home dir" do
before do
FileUtils.mkdir_p File.expand_path("~/.foreman/templates/daemon")
File.open(File.expand_path("~/.foreman/templates/daemon/master.conf.erb"), "w") do |file|
file.puts "default_alternate_template"
end
end
it "can export with alternate template files" do
daemon.export
File.read("/tmp/init/app.conf").should == "default_alternate_template\n"
end
end
end

View File

@@ -31,6 +31,6 @@ describe Foreman::Export::Runit, :fakefs do
end
it "creates a full path to the export directory" do
expect { runit.export }.to_not raise_error(Errno::ENOENT)
expect { runit.export }.to_not raise_error
end
end

View File

@@ -0,0 +1,91 @@
require "spec_helper"
require "foreman/engine"
require "foreman/export/systemd"
require "tmpdir"
describe Foreman::Export::Systemd, :fakefs do
let(:procfile) { write_procfile("/tmp/app/Procfile") }
let(:formation) { nil }
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
let(:options) { Hash.new }
let(:systemd) { Foreman::Export::Systemd.new("/tmp/init", engine, options) }
before(:each) { load_export_templates_into_fakefs("systemd") }
before(:each) { stub(systemd).say }
it "exports to the filesystem" do
systemd.export
File.read("/tmp/init/app.target").should == example_export_file("systemd/app.target")
File.read("/tmp/init/app-alpha.target").should == example_export_file("systemd/app-alpha.target")
File.read("/tmp/init/app-alpha-1.service").should == example_export_file("systemd/app-alpha-1.service")
File.read("/tmp/init/app-bravo.target").should == example_export_file("systemd/app-bravo.target")
File.read("/tmp/init/app-bravo-1.service").should == example_export_file("systemd/app-bravo-1.service")
end
it "cleans up if exporting into an existing dir" do
mock(FileUtils).rm("/tmp/init/app.target")
mock(FileUtils).rm("/tmp/init/app-alpha.target")
mock(FileUtils).rm("/tmp/init/app-alpha-1.service")
mock(FileUtils).rm("/tmp/init/app-bravo.target")
mock(FileUtils).rm("/tmp/init/app-bravo-1.service")
mock(FileUtils).rm("/tmp/init/app-foo-bar.target")
mock(FileUtils).rm("/tmp/init/app-foo-bar-1.service")
mock(FileUtils).rm("/tmp/init/app-foo_bar.target")
mock(FileUtils).rm("/tmp/init/app-foo_bar-1.service")
systemd.export
systemd.export
end
it "includes environment variables" do
engine.env['KEY'] = 'some "value"'
systemd.export
File.read("/tmp/init/app-alpha-1.service").should =~ /KEY=some "value"$/
end
context "with a formation" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
systemd.export
File.read("/tmp/init/app.target").should == example_export_file("systemd/app.target")
File.read("/tmp/init/app-alpha.target").should == example_export_file("systemd/app-alpha.target")
File.read("/tmp/init/app-alpha-1.service").should == example_export_file("systemd/app-alpha-1.service")
File.read("/tmp/init/app-alpha-2.service").should == example_export_file("systemd/app-alpha-2.service")
File.exists?("/tmp/init/app-bravo-1.service").should == false
end
end
context "with alternate templates" do
let(:template) { "/tmp/alternate" }
let(:options) { { :app => "app", :template => template } }
before do
FileUtils.mkdir_p template
File.open("#{template}/master.target.erb", "w") { |f| f.puts "alternate_template" }
end
it "can export with alternate template files" do
systemd.export
File.read("/tmp/init/app.target").should == "alternate_template\n"
end
end
context "with alternate templates from home dir" do
before do
FileUtils.mkdir_p File.expand_path("~/.foreman/templates/systemd")
File.open(File.expand_path("~/.foreman/templates/systemd/master.target.erb"), "w") do |file|
file.puts "default_alternate_template"
end
end
it "can export with alternate template files" do
systemd.export
File.read("/tmp/init/app.target").should == "default_alternate_template\n"
end
end
end

View File

@@ -41,7 +41,7 @@ describe Foreman::Process do
it "should output utf8 properly" do
process = Foreman::Process.new(resource_path("bin/utf8"))
run(process).should == "\xFF\x03\n".force_encoding('binary')
run(process).should == (Foreman.ruby_18? ? "\xFF\x03\n" : "\xFF\x03\n".force_encoding('binary'))
end
end

View File

@@ -2,3 +2,4 @@ echo: bin/echo echoing
env: bin/env FOO
test: bin/test
utf8: bin/utf8
ps: bin/echo PS env var is $PS

View File

@@ -0,0 +1,7 @@
start on starting app-alpha
stop on stopping app-alpha
respawn
env PORT=5000
exec start-stop-daemon --start --chuid app --chdir /tmp/app --make-pidfile --pidfile /var/run/app/app-alpha-1.pid --exec ./alpha >> /var/log/app/app-alpha-1.log 2>&1

View File

@@ -0,0 +1,7 @@
start on starting app-alpha
stop on stopping app-alpha
respawn
env PORT=5001
exec start-stop-daemon --start --chuid app --chdir /tmp/app --make-pidfile --pidfile /var/run/app/app-alpha-2.pid --exec ./alpha >> /var/log/app/app-alpha-2.log 2>&1

View File

@@ -0,0 +1,2 @@
start on starting app
stop on stopping app

View File

@@ -0,0 +1,7 @@
start on starting app-bravo
stop on stopping app-bravo
respawn
env PORT=5100
exec start-stop-daemon --start --chuid app --chdir /tmp/app --make-pidfile --pidfile /var/run/app/app-bravo-1.pid --exec ./bravo >> /var/log/app/app-bravo-1.log 2>&1

View File

@@ -0,0 +1,2 @@
start on starting app
stop on stopping app

View File

@@ -0,0 +1,14 @@
pre-start script
bash << "EOF"
mkdir -p /var/log/app
chown -R app /var/log/app
mkdir -p /var/run/app
chown -R app /var/run/app
EOF
end script
start on runlevel [2345]
stop on runlevel [016]

View File

@@ -0,0 +1,17 @@
[Unit]
StopWhenUnneeded=true
[Service]
User=app
WorkingDirectory=/tmp/app
Environment=PORT=5000
ExecStart=/bin/bash -lc './alpha'
Restart=always
StandardInput=null
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n
KillMode=process
[Install]
WantedBy=app-alpha.target

View File

@@ -0,0 +1,17 @@
[Unit]
StopWhenUnneeded=true
[Service]
User=app
WorkingDirectory=/tmp/app
Environment=PORT=5001
ExecStart=/bin/bash -lc './alpha'
Restart=always
StandardInput=null
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n
KillMode=process
[Install]
WantedBy=app-alpha.target

View File

@@ -0,0 +1,5 @@
[Unit]
StopWhenUnneeded=true
[Install]
WantedBy=app.target

View File

@@ -0,0 +1,17 @@
[Unit]
StopWhenUnneeded=true
[Service]
User=app
WorkingDirectory=/tmp/app
Environment=PORT=5100
ExecStart=/bin/bash -lc './bravo'
Restart=always
StandardInput=null
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n
KillMode=process
[Install]
WantedBy=app-bravo.target

View File

@@ -0,0 +1,5 @@
[Unit]
StopWhenUnneeded=true
[Install]
WantedBy=app.target

View File

@@ -0,0 +1 @@
[Unit]

View File

@@ -10,6 +10,15 @@ require "fakefs/spec_helpers"
$:.unshift File.expand_path("../../lib", __FILE__)
begin
def running_ruby_18?
defined?(RUBY_VERSION) and RUBY_VERSION =~ /^1\.8\.\d+/
end
require 'posix/spawn' if running_ruby_18?
rescue LoadError
STDERR.puts "WARNING: foreman requires gem `posix-spawn` on Ruby #{RUBY_VERSION}. Please `gem install posix-spawn`."
end
def mock_export_error(message)
lambda { yield }.should raise_error(Foreman::Export::Exception, message)
end
@@ -21,6 +30,10 @@ def mock_error(subject, message)
end
end
def make_pipe
IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
end
def foreman(args)
capture_stdout do
begin
@@ -31,14 +44,18 @@ def foreman(args)
end
def forked_foreman(args)
rd, wr = IO.pipe("BINARY")
Process.spawn("bundle exec bin/foreman #{args}", :out => wr, :err => wr)
rd, wr = make_pipe
if running_ruby_18?
POSIX::Spawn.spawn({}, "bundle exec bin/foreman #{args}", :out => wr, :err => wr)
else
Process.spawn("bundle exec bin/foreman #{args}", :out => wr, :err => wr)
end
wr.close
rd.read
end
def fork_and_capture(&blk)
rd, wr = IO.pipe("BINARY")
rd, wr = make_pipe
pid = fork do
rd.close
wr.sync = true
@@ -57,7 +74,11 @@ def fork_and_capture(&blk)
end
def fork_and_get_exitstatus(args)
pid = Process.spawn("bundle exec bin/foreman #{args}", :out => "/dev/null", :err => "/dev/null")
pid = if running_ruby_18?
POSIX::Spawn.spawn({}, "bundle exec bin/foreman #{args}", :out => "/dev/null", :err => "/dev/null")
else
Process.spawn("bundle exec bin/foreman #{args}", :out => "/dev/null", :err => "/dev/null")
end
Process.wait(pid)
$?.exitstatus
end
@@ -141,7 +162,7 @@ end
def capture_stdout
old_stdout = $stdout.dup
rd, wr = IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
rd, wr = make_pipe
$stdout = wr
yield
wr.close
@@ -156,4 +177,5 @@ RSpec.configure do |config|
config.order = 'rand'
config.include FakeFS::SpecHelpers, :fakefs
config.mock_with :rr
config.backtrace_exclusion_patterns = []
end