foreman-0.87.2/ 0000755 0000041 0000041 00000000000 13765541231 013276 5 ustar www-data www-data foreman-0.87.2/README.md 0000644 0000041 0000041 00000003513 13765541231 014557 0 ustar www-data www-data # Foreman
[](https://travis-ci.org/ddollar/foreman)
[](https://codeclimate.com/github/ddollar/foreman)
[](http://inch-ci.org/github/ddollar/foreman)
Manage Procfile-based applications
## Installation
$ gem install foreman
Ruby users should take care *not* to install foreman in their project's `Gemfile`. See this [wiki article](https://github.com/ddollar/foreman/wiki/Don't-Bundle-Foreman) for more details.
## Getting Started
* http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
## Supported Ruby versions
See [.travis.yml](.travis.yml) for a list of Ruby versions against which Foreman is tested.
## Documentation
* [man page](http://ddollar.github.io/foreman/)
* [wiki](https://github.com/ddollar/foreman/wiki)
* [changelog](https://github.com/ddollar/foreman/blob/master/Changelog.md)
## Ports
* [forego](https://github.com/ddollar/forego) - Go
* [node-foreman](https://github.com/strongloop/node-foreman) - Node.js
* [gaffer](https://github.com/jingweno/gaffer) - Java/JVM
* [goreman](https://github.com/mattn/goreman) - Go
* [honcho](https://github.com/nickstenning/honcho) - python
* [proclet](https://github.com/kazeburo/Proclet) - Perl
* [shoreman](https://github.com/chrismytton/shoreman) - shell
* [crank](https://github.com/arktisklada/crank) - Crystal
* [houseman](https://github.com/fujimura/houseman) - Haskell
* [spm](https://github.com/bytegust/spm) - Go
## Authors
#### Created and maintained by
David Dollar
#### Patches contributed by
[Contributor List](https://github.com/ddollar/foreman/contributors)
## License
Foreman is licensed under the MIT license.
See LICENSE for the full license text.
foreman-0.87.2/bin/ 0000755 0000041 0000041 00000000000 13765541231 014046 5 ustar www-data www-data foreman-0.87.2/bin/foreman-runner 0000755 0000041 0000041 00000001161 13765541231 016731 0 ustar www-data www-data #!/bin/sh
#
#/ Usage: foreman-runner [-d
] [-p] [...]
#/
#/ Run a command with exec, optionally changing directory first
set -e
error() {
echo $@ >&2
exit 1
}
usage() {
cat $0 | grep '^#/' | cut -c4-
exit
}
read_profile=""
while getopts ":hd:p" OPT; do
case $OPT in
d) cd "$OPTARG" ;;
p) read_profile="1" ;;
h) usage ;;
\?) error "invalid option: -$OPTARG" ;;
:) error "option -$OPTARG requires an argument" ;;
esac
done
shift $((OPTIND-1))
[ -z "$1" ] && usage
if [ "$read_profile" = "1" ]; then
if [ -f .profile ]; then
. ./.profile
fi
fi
exec "$@"
foreman-0.87.2/bin/foreman 0000755 0000041 0000041 00000000163 13765541231 015423 0 ustar www-data www-data #!/usr/bin/env ruby
$:.unshift File.expand_path("../../lib", __FILE__)
require "foreman/cli"
Foreman::CLI.start
foreman-0.87.2/spec/ 0000755 0000041 0000041 00000000000 13765541231 014230 5 ustar www-data www-data foreman-0.87.2/spec/helper_spec.rb 0000644 0000041 0000041 00000000702 13765541231 017045 0 ustar www-data www-data require "spec_helper"
describe "spec helpers" do
describe "#preserving_env" do
after { ENV.delete "FOO" }
it "should remove added environment vars" do
old = ENV["FOO"]
preserving_env { ENV["FOO"] = "baz" }
expect(ENV["FOO"]).to eq(old)
end
it "should reset modified environment vars" do
ENV["FOO"] = "bar"
preserving_env { ENV["FOO"] = "baz"}
expect(ENV["FOO"]).to eq("bar")
end
end
end
foreman-0.87.2/spec/resources/ 0000755 0000041 0000041 00000000000 13765541231 016242 5 ustar www-data www-data foreman-0.87.2/spec/resources/Procfile.bad 0000644 0000041 0000041 00000000031 13765541231 020447 0 ustar www-data www-data good: sleep 1
bad: false
foreman-0.87.2/spec/resources/bin/ 0000755 0000041 0000041 00000000000 13765541231 017012 5 ustar www-data www-data foreman-0.87.2/spec/resources/bin/test 0000755 0000041 0000041 00000000031 13765541231 017711 0 ustar www-data www-data #!/bin/sh
echo "testing"
foreman-0.87.2/spec/resources/bin/echo 0000755 0000041 0000041 00000000022 13765541231 017650 0 ustar www-data www-data #!/bin/sh
echo $*
foreman-0.87.2/spec/resources/bin/utf8 0000755 0000041 0000041 00000000044 13765541231 017624 0 ustar www-data www-data #!/usr/bin/env ruby
puts "\xff\x03"
foreman-0.87.2/spec/resources/bin/env 0000755 0000041 0000041 00000000027 13765541231 017527 0 ustar www-data www-data #!/bin/bash
echo ${!1}
foreman-0.87.2/spec/resources/export/ 0000755 0000041 0000041 00000000000 13765541231 017563 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/inittab/ 0000755 0000041 0000041 00000000000 13765541231 021215 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/inittab/inittab.default 0000644 0000041 0000041 00000000764 13765541231 024224 0 ustar www-data www-data # ----- foreman app processes -----
AP01:4:respawn:/bin/su - app -c 'cd /tmp/app;export PORT=5000;./alpha >> /var/log/app/alpha-1.log 2>&1'
AP02:4:respawn:/bin/su - app -c 'cd /tmp/app;export PORT=5100;./bravo >> /var/log/app/bravo-1.log 2>&1'
AP03:4:respawn:/bin/su - app -c 'cd /tmp/app;export PORT=5200;./foo_bar >> /var/log/app/foo_bar-1.log 2>&1'
AP04:4:respawn:/bin/su - app -c 'cd /tmp/app;export PORT=5300;./foo-bar >> /var/log/app/foo-bar-1.log 2>&1'
# ----- end foreman app processes -----
foreman-0.87.2/spec/resources/export/inittab/inittab.concurrency 0000644 0000041 0000041 00000000434 13765541231 025124 0 ustar www-data www-data # ----- foreman app processes -----
AP01:4:respawn:/bin/su - app -c 'cd /tmp/app;export PORT=5000;./alpha >> /var/log/app/alpha-1.log 2>&1'
AP02:4:respawn:/bin/su - app -c 'cd /tmp/app;export PORT=5001;./alpha >> /var/log/app/alpha-2.log 2>&1'
# ----- end foreman app processes -----
foreman-0.87.2/spec/resources/export/systemd/ 0000755 0000041 0000041 00000000000 13765541231 021253 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/systemd/app-bravo.1.service 0000644 0000041 0000041 00000000521 13765541231 024661 0 ustar www-data www-data [Unit]
PartOf=app.target
StopWhenUnneeded=yes
[Service]
User=app
WorkingDirectory=/tmp/app
Environment=PORT=5100
Environment=PS=bravo.1
ExecStart=/bin/bash -lc 'exec -a "app-bravo.1" ./bravo'
Restart=always
RestartSec=14s
StandardInput=null
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n
KillMode=mixed
TimeoutStopSec=5
foreman-0.87.2/spec/resources/export/systemd/app-alpha.target 0000644 0000041 0000041 00000000031 13765541231 024320 0 ustar www-data www-data [Unit]
PartOf=app.target
foreman-0.87.2/spec/resources/export/systemd/app.target 0000644 0000041 0000041 00000000207 13765541231 023242 0 ustar www-data www-data [Unit]
Wants=app-alpha.1.service app-bravo.1.service app-foo_bar.1.service app-foo-bar.1.service
[Install]
WantedBy=multi-user.target
foreman-0.87.2/spec/resources/export/systemd/app-alpha.1.service 0000644 0000041 0000041 00000000521 13765541231 024635 0 ustar www-data www-data [Unit]
PartOf=app.target
StopWhenUnneeded=yes
[Service]
User=app
WorkingDirectory=/tmp/app
Environment=PORT=5000
Environment=PS=alpha.1
ExecStart=/bin/bash -lc 'exec -a "app-alpha.1" ./alpha'
Restart=always
RestartSec=14s
StandardInput=null
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n
KillMode=mixed
TimeoutStopSec=5
foreman-0.87.2/spec/resources/export/systemd/app-bravo.target 0000644 0000041 0000041 00000000031 13765541231 024344 0 ustar www-data www-data [Unit]
PartOf=app.target
foreman-0.87.2/spec/resources/export/systemd/app-alpha.2.service 0000644 0000041 0000041 00000000521 13765541231 024636 0 ustar www-data www-data [Unit]
PartOf=app.target
StopWhenUnneeded=yes
[Service]
User=app
WorkingDirectory=/tmp/app
Environment=PORT=5001
Environment=PS=alpha.2
ExecStart=/bin/bash -lc 'exec -a "app-alpha.2" ./alpha'
Restart=always
RestartSec=14s
StandardInput=null
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n
KillMode=mixed
TimeoutStopSec=5
foreman-0.87.2/spec/resources/export/supervisord/ 0000755 0000041 0000041 00000000000 13765541231 022150 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/supervisord/app-alpha-1.conf 0000644 0000041 0000041 00000002161 13765541231 025020 0 ustar www-data www-data [program:app-alpha-1]
command=./alpha
autostart=true
autorestart=true
stdout_logfile=/var/log/app/alpha-1.log
stderr_logfile=/var/log/app/alpha-1.error.log
user=app
directory=/tmp/app
environment=FOO="bar",URL="http://example.com/api?foo=bar&baz=1",PORT="5000"
[program:app-bravo-1]
command=./bravo
autostart=true
autorestart=true
stdout_logfile=/var/log/app/bravo-1.log
stderr_logfile=/var/log/app/bravo-1.error.log
user=app
directory=/tmp/app
environment=FOO="bar",URL="http://example.com/api?foo=bar&baz=1",PORT="5100"
[program:app-foo_bar-1]
command=./foo_bar
autostart=true
autorestart=true
stdout_logfile=/var/log/app/foo_bar-1.log
stderr_logfile=/var/log/app/foo_bar-1.error.log
user=app
directory=/tmp/app
environment=FOO="bar",URL="http://example.com/api?foo=bar&baz=1",PORT="5200"
[program:app-foo-bar-1]
command=./foo-bar
autostart=true
autorestart=true
stdout_logfile=/var/log/app/foo-bar-1.log
stderr_logfile=/var/log/app/foo-bar-1.error.log
user=app
directory=/tmp/app
environment=FOO="bar",URL="http://example.com/api?foo=bar&baz=1",PORT="5300"
[group:app]
programs=app-alpha-1,app-bravo-1,app-foo_bar-1,app-foo-bar-1
foreman-0.87.2/spec/resources/export/supervisord/app-alpha-2.conf 0000644 0000041 0000041 00000000717 13765541231 025026 0 ustar www-data www-data [program:app-alpha-1]
command=./alpha
autostart=true
autorestart=true
stdout_logfile=/var/log/app/alpha-1.log
stderr_logfile=/var/log/app/alpha-1.error.log
user=app
directory=/tmp/app
environment=PORT="5000"
[program:app-alpha-2]
command=./alpha
autostart=true
autorestart=true
stdout_logfile=/var/log/app/alpha-2.log
stderr_logfile=/var/log/app/alpha-2.error.log
user=app
directory=/tmp/app
environment=PORT="5001"
[group:app]
programs=app-alpha-1,app-alpha-2
foreman-0.87.2/spec/resources/export/runit/ 0000755 0000041 0000041 00000000000 13765541231 020724 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/runit/app-alpha-2/ 0000755 0000041 0000041 00000000000 13765541231 022726 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/runit/app-alpha-2/run 0000644 0000041 0000041 00000000137 13765541231 023456 0 ustar www-data www-data #!/bin/sh
cd /tmp/app
exec 2>&1
exec chpst -u app -e /tmp/init/app-alpha-2/env ./alpha bar=baz
foreman-0.87.2/spec/resources/export/runit/app-alpha-2/log/ 0000755 0000041 0000041 00000000000 13765541231 023507 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/runit/app-alpha-2/log/run 0000644 0000041 0000041 00000000212 13765541231 024231 0 ustar www-data www-data #!/bin/sh
set -e
LOG=/var/log/app/alpha-2
test -d "$LOG" || mkdir -p -m 2750 "$LOG" && chown app "$LOG"
exec chpst -u app svlogd "$LOG"
foreman-0.87.2/spec/resources/export/runit/app-alpha-1/ 0000755 0000041 0000041 00000000000 13765541231 022725 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/runit/app-alpha-1/run 0000644 0000041 0000041 00000000137 13765541231 023455 0 ustar www-data www-data #!/bin/sh
cd /tmp/app
exec 2>&1
exec chpst -u app -e /tmp/init/app-alpha-1/env ./alpha bar=baz
foreman-0.87.2/spec/resources/export/runit/app-alpha-1/log/ 0000755 0000041 0000041 00000000000 13765541231 023506 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/runit/app-alpha-1/log/run 0000644 0000041 0000041 00000000212 13765541231 024230 0 ustar www-data www-data #!/bin/sh
set -e
LOG=/var/log/app/alpha-1
test -d "$LOG" || mkdir -p -m 2750 "$LOG" && chown app "$LOG"
exec chpst -u app svlogd "$LOG"
foreman-0.87.2/spec/resources/export/runit/app-bravo-1/ 0000755 0000041 0000041 00000000000 13765541231 022751 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/runit/app-bravo-1/run 0000644 0000041 0000041 00000000127 13765541231 023500 0 ustar www-data www-data #!/bin/sh
cd /tmp/app
exec 2>&1
exec chpst -u app -e /tmp/init/app-bravo-1/env ./bravo
foreman-0.87.2/spec/resources/export/runit/app-bravo-1/log/ 0000755 0000041 0000041 00000000000 13765541231 023532 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/runit/app-bravo-1/log/run 0000644 0000041 0000041 00000000212 13765541231 024254 0 ustar www-data www-data #!/bin/sh
set -e
LOG=/var/log/app/bravo-1
test -d "$LOG" || mkdir -p -m 2750 "$LOG" && chown app "$LOG"
exec chpst -u app svlogd "$LOG"
foreman-0.87.2/spec/resources/export/upstart/ 0000755 0000041 0000041 00000000000 13765541231 021265 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/upstart/app-alpha-1.conf 0000644 0000041 0000041 00000000170 13765541231 024133 0 ustar www-data www-data start on starting app-alpha
stop on stopping app-alpha
respawn
env PORT=5000
setuid app
chdir /tmp/app
exec ./alpha
foreman-0.87.2/spec/resources/export/upstart/app.conf 0000644 0000041 0000041 00000000062 13765541231 022712 0 ustar www-data www-data start on runlevel [2345]
stop on runlevel [!2345]
foreman-0.87.2/spec/resources/export/upstart/app-alpha-2.conf 0000644 0000041 0000041 00000000170 13765541231 024134 0 ustar www-data www-data start on starting app-alpha
stop on stopping app-alpha
respawn
env PORT=5001
setuid app
chdir /tmp/app
exec ./alpha
foreman-0.87.2/spec/resources/export/upstart/app-bravo-1.conf 0000644 0000041 0000041 00000000170 13765541231 024157 0 ustar www-data www-data start on starting app-bravo
stop on stopping app-bravo
respawn
env PORT=5100
setuid app
chdir /tmp/app
exec ./bravo
foreman-0.87.2/spec/resources/export/upstart/app-alpha.conf 0000644 0000041 0000041 00000000053 13765541231 023775 0 ustar www-data www-data start on starting app
stop on stopping app
foreman-0.87.2/spec/resources/export/upstart/app-bravo.conf 0000644 0000041 0000041 00000000053 13765541231 024021 0 ustar www-data www-data start on starting app
stop on stopping app
foreman-0.87.2/spec/resources/export/launchd/ 0000755 0000041 0000041 00000000000 13765541231 021201 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/launchd/launchd-a.default 0000644 0000041 0000041 00000001432 13765541231 024403 0 ustar www-data www-data
Labelapp-alpha-1EnvironmentVariablesPORT5000ProgramArguments./alphaKeepAliveRunAtLoadStandardOutPath/var/log/app/app-alpha-1.logStandardErrorPath/var/log/app/app-alpha-1.logUserNameappWorkingDirectory/tmp/app
foreman-0.87.2/spec/resources/export/launchd/launchd-c.default 0000644 0000041 0000041 00000001473 13765541231 024412 0 ustar www-data www-data
Labelapp-alpha-1EnvironmentVariablesPORT5000ProgramArguments./alphacharlieKeepAliveRunAtLoadStandardOutPath/var/log/app/app-alpha-1.logStandardErrorPath/var/log/app/app-alpha-1.logUserNameappWorkingDirectory/tmp/app
foreman-0.87.2/spec/resources/export/launchd/launchd-b.default 0000644 0000041 0000041 00000001432 13765541231 024404 0 ustar www-data www-data
Labelapp-bravo-1EnvironmentVariablesPORT5100ProgramArguments./bravoKeepAliveRunAtLoadStandardOutPath/var/log/app/app-bravo-1.logStandardErrorPath/var/log/app/app-bravo-1.logUserNameappWorkingDirectory/tmp/app
foreman-0.87.2/spec/resources/export/daemon/ 0000755 0000041 0000041 00000000000 13765541231 021026 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/daemon/app-alpha-1.conf 0000644 0000041 0000041 00000000365 13765541231 023702 0 ustar www-data www-data 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
foreman-0.87.2/spec/resources/export/daemon/app.conf 0000644 0000041 0000041 00000000302 13765541231 022450 0 ustar www-data www-data 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]
foreman-0.87.2/spec/resources/export/daemon/app-alpha-2.conf 0000644 0000041 0000041 00000000365 13765541231 023703 0 ustar www-data www-data 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
foreman-0.87.2/spec/resources/export/daemon/app-bravo-1.conf 0000644 0000041 0000041 00000000365 13765541231 023726 0 ustar www-data www-data 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
foreman-0.87.2/spec/resources/export/daemon/app-alpha.conf 0000644 0000041 0000041 00000000053 13765541231 023536 0 ustar www-data www-data start on starting app
stop on stopping app
foreman-0.87.2/spec/resources/export/daemon/app-bravo.conf 0000644 0000041 0000041 00000000053 13765541231 023562 0 ustar www-data www-data start on starting app
stop on stopping app
foreman-0.87.2/spec/resources/export/bluepill/ 0000755 0000041 0000041 00000000000 13765541231 021373 5 ustar www-data www-data foreman-0.87.2/spec/resources/export/bluepill/app-concurrency.pill 0000644 0000041 0000041 00000002216 13765541231 025366 0 ustar www-data www-data Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
app.uid = "app"
app.gid = "app"
app.process("alpha-1") do |process|
process.start_command = %Q{./alpha}
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT"=>"5000"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stop_grace_time = 45.seconds
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
process.monitor_children do |children|
children.stop_command "kill {{PID}}"
end
process.group = "app-alpha"
end
app.process("alpha-2") do |process|
process.start_command = %Q{./alpha}
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT"=>"5001"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stop_grace_time = 45.seconds
process.stdout = process.stderr = "/var/log/app/app-alpha-2.log"
process.monitor_children do |children|
children.stop_command "kill {{PID}}"
end
process.group = "app-alpha"
end
end
foreman-0.87.2/spec/resources/export/bluepill/app.pill 0000644 0000041 0000041 00000004204 13765541231 023035 0 ustar www-data www-data Bluepill.application("app", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
app.uid = "app"
app.gid = "app"
app.process("alpha-1") do |process|
process.start_command = %Q{./alpha}
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT"=>"5000"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stop_grace_time = 45.seconds
process.stdout = process.stderr = "/var/log/app/app-alpha-1.log"
process.monitor_children do |children|
children.stop_command "kill {{PID}}"
end
process.group = "app-alpha"
end
app.process("bravo-1") do |process|
process.start_command = %Q{./bravo}
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT"=>"5100"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stop_grace_time = 45.seconds
process.stdout = process.stderr = "/var/log/app/app-bravo-1.log"
process.monitor_children do |children|
children.stop_command "kill {{PID}}"
end
process.group = "app-bravo"
end
app.process("foo_bar-1") do |process|
process.start_command = %Q{./foo_bar}
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT"=>"5200"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stop_grace_time = 45.seconds
process.stdout = process.stderr = "/var/log/app/app-foo_bar-1.log"
process.monitor_children do |children|
children.stop_command "kill {{PID}}"
end
process.group = "app-foo_bar"
end
app.process("foo-bar-1") do |process|
process.start_command = %Q{./foo-bar}
process.working_dir = "/tmp/app"
process.daemonize = true
process.environment = {"PORT"=>"5300"}
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stop_grace_time = 45.seconds
process.stdout = process.stderr = "/var/log/app/app-foo-bar-1.log"
process.monitor_children do |children|
children.stop_command "kill {{PID}}"
end
process.group = "app-foo-bar"
end
end
foreman-0.87.2/spec/resources/Procfile 0000644 0000041 0000041 00000000151 13765541231 017725 0 ustar www-data www-data echo: bin/echo echoing
env: bin/env FOO
test: bin/test
utf8: bin/utf8
ps: bin/echo PS env var is $PS
foreman-0.87.2/spec/foreman_spec.rb 0000644 0000041 0000041 00000000435 13765541231 017220 0 ustar www-data www-data require "spec_helper"
require "foreman"
describe Foreman do
describe "VERSION" do
subject { Foreman::VERSION }
it { is_expected.to be_a String }
end
describe "runner" do
it "should exist" do
expect(File.exists?(Foreman.runner)).to eq(true)
end
end
end
foreman-0.87.2/spec/foreman/ 0000755 0000041 0000041 00000000000 13765541231 015657 5 ustar www-data www-data foreman-0.87.2/spec/foreman/engine_spec.rb 0000644 0000041 0000041 00000006404 13765541231 020467 0 ustar www-data www-data require "spec_helper"
require "foreman/engine"
class Foreman::Engine::Tester < Foreman::Engine
attr_reader :buffer
def startup
@buffer = ""
end
def output(name, data)
@buffer += "#{name}: #{data}"
end
def shutdown
end
end
describe "Foreman::Engine", :fakefs do
subject do
write_procfile "Procfile"
Foreman::Engine::Tester.new.load_procfile("Procfile")
end
describe "initialize" do
describe "with a Procfile" do
before { write_procfile }
it "reads the processes" do
expect(subject.process("alpha").command).to eq("./alpha")
expect(subject.process("bravo").command).to eq("./bravo")
end
end
end
describe "start" do
it "forks the processes" do
expect(subject.process("alpha")).to receive(:run)
expect(subject.process("bravo")).to receive(:run)
expect(subject).to receive(:watch_for_output)
expect(subject).to receive(:wait_for_shutdown_or_child_termination)
subject.start
end
it "handles concurrency" do
subject.options[:formation] = "alpha=2"
expect(subject.process("alpha")).to receive(:run).twice
expect(subject.process("bravo")).to_not receive(:run)
expect(subject).to receive(:watch_for_output)
expect(subject).to receive(:wait_for_shutdown_or_child_termination)
subject.start
end
end
describe "directories" do
it "has the directory default relative to the Procfile" do
write_procfile "/some/app/Procfile"
engine = Foreman::Engine.new.load_procfile("/some/app/Procfile")
expect(engine.root).to eq("/some/app")
end
end
describe "environment" do
it "should read env files" do
write_file("/tmp/env") { |f| f.puts("FOO=baz") }
subject.load_env("/tmp/env")
expect(subject.env["FOO"]).to eq("baz")
end
it "should read more than one if specified" do
write_file("/tmp/env1") { |f| f.puts("FOO=bar") }
write_file("/tmp/env2") { |f| f.puts("BAZ=qux") }
subject.load_env "/tmp/env1"
subject.load_env "/tmp/env2"
expect(subject.env["FOO"]).to eq("bar")
expect(subject.env["BAZ"]).to eq("qux")
end
it "should handle quoted values" do
write_file("/tmp/env") do |f|
f.puts 'FOO=bar'
f.puts 'BAZ="qux"'
f.puts "FRED='barney'"
f.puts 'OTHER="escaped\"quote"'
f.puts 'URL="http://example.com/api?foo=bar&baz=1"'
end
subject.load_env "/tmp/env"
expect(subject.env["FOO"]).to eq("bar")
expect(subject.env["BAZ"]).to eq("qux")
expect(subject.env["FRED"]).to eq("barney")
expect(subject.env["OTHER"]).to eq('escaped"quote')
expect(subject.env["URL"]).to eq("http://example.com/api?foo=bar&baz=1")
end
it "should handle multiline strings" do
write_file("/tmp/env") do |f|
f.puts 'FOO="bar\nbaz"'
end
subject.load_env "/tmp/env"
expect(subject.env["FOO"]).to eq("bar\nbaz")
end
it "should fail if specified and doesnt exist" do
expect { subject.load_env "/tmp/env" }.to raise_error(Errno::ENOENT)
end
it "should set port from .env if specified" do
write_file("/tmp/env") { |f| f.puts("PORT=9000") }
subject.load_env "/tmp/env"
expect(subject.send(:base_port)).to eq(9000)
end
end
end
foreman-0.87.2/spec/foreman/helpers_spec.rb 0000644 0000041 0000041 00000001064 13765541231 020661 0 ustar www-data www-data require "spec_helper"
require "foreman/helpers"
describe "Foreman::Helpers" do
before do
module Foo
class Bar; end
end
end
after do
Object.send(:remove_const, :Foo)
end
subject { o = Object.new; o.extend(Foreman::Helpers); o }
it "should classify words" do
expect(subject.classify("foo")).to eq("Foo")
expect(subject.classify("foo-bar")).to eq("FooBar")
end
it "should constantize words" do
expect(subject.constantize("Object")).to eq(Object)
expect(subject.constantize("Foo::Bar")).to eq(Foo::Bar)
end
end
foreman-0.87.2/spec/foreman/export/ 0000755 0000041 0000041 00000000000 13765541231 017200 5 ustar www-data www-data foreman-0.87.2/spec/foreman/export/base_spec.rb 0000644 0000041 0000041 00000001252 13765541231 021451 0 ustar www-data www-data require "spec_helper"
require "foreman/engine"
require "foreman/export"
describe "Foreman::Export::Base", :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:location) { "/tmp/init" }
let(:engine) { Foreman::Engine.new().load_procfile(procfile) }
let(:subject) { Foreman::Export::Base.new(location, engine) }
it "has a say method for displaying info" do
expect(subject).to receive(:puts).with("[foreman export] foo")
subject.send(:say, "foo")
end
it "raises errors as a Foreman::Export::Exception" do
expect { subject.send(:error, "foo") }.to raise_error(Foreman::Export::Exception, "foo")
end
end
foreman-0.87.2/spec/foreman/export/inittab_spec.rb 0000644 0000041 0000041 00000002331 13765541231 022170 0 ustar www-data www-data require "spec_helper"
require "foreman/engine"
require "foreman/export/inittab"
require "tmpdir"
describe Foreman::Export::Inittab, :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:location) { "/tmp/inittab" }
let(:formation) { nil }
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
let(:options) { Hash.new }
let(:inittab) { Foreman::Export::Inittab.new(location, engine, options) }
before(:each) { load_export_templates_into_fakefs("inittab") }
before(:each) { allow(inittab).to receive(:say) }
it "exports to the filesystem" do
inittab.export
expect(File.read("/tmp/inittab")).to eq(example_export_file("inittab/inittab.default"))
end
context "to stdout" do
let(:location) { "-" }
it "exports to stdout" do
expect(inittab).to receive(:puts).with(example_export_file("inittab/inittab.default"))
inittab.export
end
end
context "with concurrency" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
inittab.export
expect(File.read("/tmp/inittab")).to eq(example_export_file("inittab/inittab.concurrency"))
end
end
end
foreman-0.87.2/spec/foreman/export/supervisord_spec.rb 0000644 0000041 0000041 00000002502 13765541231 023123 0 ustar www-data www-data require "spec_helper"
require "foreman/engine"
require "foreman/export/supervisord"
require "tmpdir"
describe Foreman::Export::Supervisord, :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:formation) { nil }
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
let(:options) { Hash.new }
let(:supervisord) { Foreman::Export::Supervisord.new("/tmp/init", engine, options) }
before(:each) { load_export_templates_into_fakefs("supervisord") }
before(:each) { allow(supervisord).to receive(:say) }
it "exports to the filesystem" do
write_env(".env", "FOO"=>"bar", "URL"=>"http://example.com/api?foo=bar&baz=1")
supervisord.engine.load_env('.env')
supervisord.export
expect(File.read("/tmp/init/app.conf")).to eq(example_export_file("supervisord/app-alpha-1.conf"))
end
it "cleans up if exporting into an existing dir" do
expect(FileUtils).to receive(:rm).with("/tmp/init/app.conf")
supervisord.export
supervisord.export
end
context "with concurrency" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
supervisord.export
expect(File.read("/tmp/init/app.conf")).to eq(example_export_file("supervisord/app-alpha-2.conf"))
end
end
end
foreman-0.87.2/spec/foreman/export/runit_spec.rb 0000644 0000041 0000041 00000003464 13765541231 021707 0 ustar www-data www-data require "spec_helper"
require "foreman/engine"
require "foreman/export/runit"
require "tmpdir"
describe Foreman::Export::Runit, :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile", 'bar=baz') }
let(:engine) { Foreman::Engine.new(:formation => "alpha=2,bravo=1").load_procfile(procfile) }
let(:options) { Hash.new }
let(:runit) { Foreman::Export::Runit.new('/tmp/init', engine, options) }
before(:each) { load_export_templates_into_fakefs("runit") }
before(:each) { allow(runit).to receive(:say) }
before(:each) { allow(FakeFS::FileUtils).to receive(:chmod) }
it "exports to the filesystem" do
engine.env["BAR"] = "baz"
runit.export
expect(File.read("/tmp/init/app-alpha-1/run")).to eq(example_export_file('runit/app-alpha-1/run'))
expect(File.read("/tmp/init/app-alpha-1/log/run")).to eq(example_export_file('runit/app-alpha-1/log/run'))
expect(File.read("/tmp/init/app-alpha-1/env/PORT")).to eq("5000\n")
expect(File.read("/tmp/init/app-alpha-1/env/BAR")).to eq("baz\n")
expect(File.read("/tmp/init/app-alpha-2/run")).to eq(example_export_file('runit/app-alpha-2/run'))
expect(File.read("/tmp/init/app-alpha-2/log/run")).to eq(example_export_file('runit/app-alpha-2/log/run'))
expect(File.read("/tmp/init/app-alpha-2/env/PORT")).to eq("5001\n")
expect(File.read("/tmp/init/app-alpha-2/env/BAR")).to eq("baz\n")
expect(File.read("/tmp/init/app-bravo-1/run")).to eq(example_export_file('runit/app-bravo-1/run'))
expect(File.read("/tmp/init/app-bravo-1/log/run")).to eq(example_export_file('runit/app-bravo-1/log/run'))
expect(File.read("/tmp/init/app-bravo-1/env/PORT")).to eq("5100\n")
end
it "creates a full path to the export directory" do
expect { runit.export }.to_not raise_error
end
end
foreman-0.87.2/spec/foreman/export/upstart_spec.rb 0000644 0000041 0000041 00000010626 13765541231 022246 0 ustar www-data www-data require "spec_helper"
require "foreman/engine"
require "foreman/export/upstart"
require "tmpdir"
describe Foreman::Export::Upstart, :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(:upstart) { Foreman::Export::Upstart.new("/tmp/init", engine, options) }
before(:each) { load_export_templates_into_fakefs("upstart") }
before(:each) { allow(upstart).to receive(:say) }
it "exports to the filesystem" do
upstart.export
expect(File.read("/tmp/init/app.conf")).to eq(example_export_file("upstart/app.conf"))
expect(File.read("/tmp/init/app-alpha.conf")).to eq(example_export_file("upstart/app-alpha.conf"))
expect(File.read("/tmp/init/app-alpha-1.conf")).to eq(example_export_file("upstart/app-alpha-1.conf"))
expect(File.read("/tmp/init/app-bravo.conf")).to eq(example_export_file("upstart/app-bravo.conf"))
expect(File.read("/tmp/init/app-bravo-1.conf")).to eq(example_export_file("upstart/app-bravo-1.conf"))
end
it "cleans up if exporting into an existing dir" do
expect(FileUtils).to receive(:rm).with("/tmp/init/app.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha-1.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo-1.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar-1.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar-1.conf")
upstart.export
upstart.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)
expect(FileUtils).to_not receive(:rm).with(path)
end
upstart.export
end
it 'does not delete exported files for app which share name prefix' do
FileUtils.mkdir_p "/tmp/init"
["app-worker", "app-worker-worker", "app-worker-worker-1"].each do |name|
path = "/tmp/init/#{name}.conf"
FileUtils.touch(path)
expect(FileUtils).to_not receive(:rm).with(path)
end
upstart.export
expect(File.exist?('/tmp/init/app.conf')).to be true
expect(File.exist?('/tmp/init/app-worker.conf')).to be true
end
it "quotes and escapes environment variables" do
engine.env['KEY'] = 'd"\|d'
upstart.export
expect("foobarfoo").to include "bar"
expect(File.read("/tmp/init/app-alpha-1.conf")).to match(/KEY='d"\\\|d'/)
end
context "with a formation" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
upstart.export
expect(File.read("/tmp/init/app.conf")).to eq(example_export_file("upstart/app.conf"))
expect(File.read("/tmp/init/app-alpha.conf")).to eq(example_export_file("upstart/app-alpha.conf"))
expect(File.read("/tmp/init/app-alpha-1.conf")).to eq(example_export_file("upstart/app-alpha-1.conf"))
expect(File.read("/tmp/init/app-alpha-2.conf")).to eq(example_export_file("upstart/app-alpha-2.conf"))
expect(File.exists?("/tmp/init/app-bravo-1.conf")).to eq(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
upstart.export
expect(File.read("/tmp/init/app.conf")).to eq("alternate_template\n")
end
end
context "with alternate templates from home dir" do
before do
FileUtils.mkdir_p File.expand_path("~/.foreman/templates/upstart")
File.open(File.expand_path("~/.foreman/templates/upstart/master.conf.erb"), "w") do |file|
file.puts "default_alternate_template"
end
end
it "can export with alternate template files" do
upstart.export
expect(File.read("/tmp/init/app.conf")).to eq("default_alternate_template\n")
end
end
end
foreman-0.87.2/spec/foreman/export/launchd_spec.rb 0000644 0000041 0000041 00000002176 13765541231 022163 0 ustar www-data www-data require "spec_helper"
require "foreman/engine"
require "foreman/export/launchd"
require "tmpdir"
describe Foreman::Export::Launchd, :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:options) { Hash.new }
let(:engine) { Foreman::Engine.new().load_procfile(procfile) }
let(:launchd) { Foreman::Export::Launchd.new("/tmp/init", engine, options) }
before(:each) { load_export_templates_into_fakefs("launchd") }
before(:each) { allow(launchd).to receive(:say) }
it "exports to the filesystem" do
launchd.export
expect(File.read("/tmp/init/app-alpha-1.plist")).to eq(example_export_file("launchd/launchd-a.default"))
expect(File.read("/tmp/init/app-bravo-1.plist")).to eq(example_export_file("launchd/launchd-b.default"))
end
context "with multiple command arguments" do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile", "charlie") }
it "splits each command argument" do
launchd.export
expect(File.read("/tmp/init/app-alpha-1.plist")).to eq(example_export_file("launchd/launchd-c.default"))
end
end
end
foreman-0.87.2/spec/foreman/export/bluepill_spec.rb 0000644 0000041 0000041 00000002345 13765541231 022353 0 ustar www-data www-data require "spec_helper"
require "foreman/engine"
require "foreman/export/bluepill"
require "tmpdir"
describe Foreman::Export::Bluepill, :fakefs do
let(:procfile) { FileUtils.mkdir_p("/tmp/app"); write_procfile("/tmp/app/Procfile") }
let(:formation) { nil }
let(:engine) { Foreman::Engine.new(:formation => formation).load_procfile(procfile) }
let(:options) { Hash.new }
let(:bluepill) { Foreman::Export::Bluepill.new("/tmp/init", engine, options) }
before(:each) { load_export_templates_into_fakefs("bluepill") }
before(:each) { allow(bluepill).to receive(:say) }
it "exports to the filesystem" do
bluepill.export
expect(normalize_space(File.read("/tmp/init/app.pill"))).to eq(normalize_space(example_export_file("bluepill/app.pill")))
end
it "cleans up if exporting into an existing dir" do
expect(FileUtils).to receive(:rm).with("/tmp/init/app.pill")
bluepill.export
bluepill.export
end
context "with a process formation" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
bluepill.export
expect(normalize_space(File.read("/tmp/init/app.pill"))).to eq(normalize_space(example_export_file("bluepill/app-concurrency.pill")))
end
end
end
foreman-0.87.2/spec/foreman/export/systemd_spec.rb 0000644 0000041 0000041 00000014443 13765541231 022235 0 ustar www-data www-data require "spec_helper"
require "foreman/engine"
require "foreman/export/systemd"
require "tmpdir"
describe Foreman::Export::Systemd, :fakefs, :aggregate_failures 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) { allow(systemd).to receive(:say) }
it "exports to the filesystem" do
systemd.export
expect(File.read("/tmp/init/app.target")).to eq(example_export_file("systemd/app.target"))
expect(File.read("/tmp/init/app-alpha.1.service")).to eq(example_export_file("systemd/app-alpha.1.service"))
expect(File.read("/tmp/init/app-bravo.1.service")).to eq(example_export_file("systemd/app-bravo.1.service"))
end
context "when systemd export was run using the previous version of systemd export" do
before do
write_file("/tmp/init/app.target")
write_file("/tmp/init/app-alpha@.service")
write_file("/tmp/init/app-alpha.target")
write_file("/tmp/init/app-alpha.target.wants/app-alpha@5000.service")
write_file("/tmp/init/app-bravo.target")
write_file("/tmp/init/app-bravo@.service")
write_file("/tmp/init/app-bravo.target.wants/app-bravo@5100.service")
write_file("/tmp/init/app-foo_bar.target")
write_file("/tmp/init/app-foo_bar@.service")
write_file("/tmp/init/app-foo_bar.target.wants/app-foo_bar@5200.service")
write_file("/tmp/init/app-foo-bar.target")
write_file("/tmp/init/app-foo-bar@.service")
write_file("/tmp/init/app-foo-bar.target.wants/app-foo-bar@5300.service")
end
it "cleans up service files created by systemd export" do
expect(FileUtils).to receive(:rm).with("/tmp/init/app.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha@.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.target.wants/app-alpha@5000.service")
expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-alpha.target.wants")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo@.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.target.wants/app-bravo@5100.service")
expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-bravo.target.wants")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar@.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.target.wants/app-foo_bar@5200.service")
expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-foo_bar.target.wants")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar@.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.target.wants/app-foo-bar@5300.service")
expect(FileUtils).to receive(:rm_r).with("/tmp/init/app-foo-bar.target.wants")
systemd.export
end
end
context "when systemd export was run using the current version of systemd export" do
before do
systemd.export
end
it "cleans up service files created by systemd export" do
expect(FileUtils).to receive(:rm).with("/tmp/init/app.target")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.1.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.1.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.1.service")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.1.service")
systemd.export
end
end
it "includes environment variables" do
engine.env['KEY'] = 'some "value"'
systemd.export
expect(File.read("/tmp/init/app-alpha.1.service")).to match(/KEY=some "value"/)
end
it "includes ExecStart line" do
engine.env['KEY'] = 'some "value"'
systemd.export
expect(File.read("/tmp/init/app-alpha.1.service")).to match(/^ExecStart=/)
end
context "with a custom formation specified" do
let(:formation) { "alpha=2" }
it "exports only those services that are specified in the formation" do
systemd.export
expect(File.read("/tmp/init/app.target")).to include("Wants=app-alpha.1.service app-alpha.2.service\n")
expect(File.read("/tmp/init/app-alpha.1.service")).to eq(example_export_file("systemd/app-alpha.1.service"))
expect(File.read("/tmp/init/app-alpha.2.service")).to eq(example_export_file("systemd/app-alpha.2.service"))
expect(File.exist?("/tmp/init/app-bravo.1.service")).to be_falsey
end
end
context "with alternate template directory specified" 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 "uses template files found in the alternate directory" do
systemd.export
expect(File.read("/tmp/init/app.target")).to eq("alternate_template\n")
end
context "with alternate templates in the user home directory" 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 "home_dir_template"
end
end
it "uses template files found in the alternate directory" do
systemd.export
expect(File.read("/tmp/init/app.target")).to eq("alternate_template\n")
end
end
end
context "with alternate templates in the user home directory" 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 "home_dir_template"
end
end
it "uses template files found in the user home directory" do
systemd.export
expect(File.read("/tmp/init/app.target")).to eq("home_dir_template\n")
end
end
end
foreman-0.87.2/spec/foreman/export/daemon_spec.rb 0000644 0000041 0000041 00000007313 13765541231 022006 0 ustar www-data www-data 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) { allow(daemon).to receive(:say) }
it "exports to the filesystem" do
daemon.export
expect(File.read("/tmp/init/app.conf")).to eq(example_export_file("daemon/app.conf"))
expect(File.read("/tmp/init/app-alpha.conf")).to eq(example_export_file("daemon/app-alpha.conf"))
expect(File.read("/tmp/init/app-alpha-1.conf")).to eq(example_export_file("daemon/app-alpha-1.conf"))
expect(File.read("/tmp/init/app-bravo.conf")).to eq(example_export_file("daemon/app-bravo.conf"))
expect(File.read("/tmp/init/app-bravo-1.conf")).to eq(example_export_file("daemon/app-bravo-1.conf"))
end
it "cleans up if exporting into an existing dir" do
expect(FileUtils).to receive(:rm).with("/tmp/init/app.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-alpha-1.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-bravo-1.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo-bar-1.conf")
expect(FileUtils).to receive(:rm).with("/tmp/init/app-foo_bar.conf")
expect(FileUtils).to receive(:rm).with("/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)
expect(FileUtils).to_not receive(:rm).with(path)
end
daemon.export
end
context "with a formation" do
let(:formation) { "alpha=2" }
it "exports to the filesystem with concurrency" do
daemon.export
expect(File.read("/tmp/init/app.conf")).to eq(example_export_file("daemon/app.conf"))
expect(File.read("/tmp/init/app-alpha.conf")).to eq(example_export_file("daemon/app-alpha.conf"))
expect(File.read("/tmp/init/app-alpha-1.conf")).to eq(example_export_file("daemon/app-alpha-1.conf"))
expect(File.read("/tmp/init/app-alpha-2.conf")).to eq(example_export_file("daemon/app-alpha-2.conf"))
expect(File.exists?("/tmp/init/app-bravo-1.conf")).to eq(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
expect(File.read("/tmp/init/app.conf")).to eq("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
expect(File.read("/tmp/init/app.conf")).to eq("default_alternate_template\n")
end
end
end
foreman-0.87.2/spec/foreman/cli_spec.rb 0000644 0000041 0000041 00000006560 13765541231 017774 0 ustar www-data www-data require "spec_helper"
require "foreman/cli"
describe "Foreman::CLI", :fakefs do
subject { Foreman::CLI.new }
describe ".foreman" do
before { File.open(".foreman", "w") { |f| f.puts "formation: alpha=2" } }
it "provides default options" do
expect(subject.send(:options)["formation"]).to eq("alpha=2")
end
it "is overridden by options at the cli" do
subject = Foreman::CLI.new([], :formation => "alpha=3")
expect(subject.send(:options)["formation"]).to eq("alpha=3")
end
end
describe "start" do
describe "when a Procfile doesnt exist", :fakefs do
it "displays an error" do
mock_error(subject, "Procfile does not exist.") do
expect_any_instance_of(Foreman::Engine).to_not receive(:start)
subject.start
end
end
end
describe "with a valid Procfile" do
it "can run a single command" do
without_fakefs do
output = foreman("start env -f #{resource_path("Procfile")}")
expect(output).to match(/env.1/)
expect(output).not_to match(/test.1/)
end
end
it "can run all commands" do
without_fakefs do
output = foreman("start -f #{resource_path("Procfile")} -e #{resource_path(".env")}")
expect(output).to match(/echo.1 \| echoing/)
expect(output).to match(/env.1 \| bar/)
expect(output).to match(/test.1 \| testing/)
end
end
it "sets PS variable with the process name" do
without_fakefs do
output = foreman("start -f #{resource_path("Procfile")}")
expect(output).to match(/ps.1 \| PS env var is ps.1/)
end
end
it "fails if process fails" do
output = `bundle exec foreman start -f #{resource_path "Procfile.bad"} && echo success`
expect(output).not_to include 'success'
end
end
end
describe "check" do
it "with a valid Procfile displays the jobs" do
write_procfile
expect(foreman("check")).to eq("valid procfile detected (alpha, bravo, foo_bar, foo-bar)\n")
end
it "with a blank Procfile displays an error" do
FileUtils.touch "Procfile"
expect(foreman("check")).to eq("ERROR: no processes defined\n")
end
it "without a Procfile displays an error" do
expect(foreman("check")).to eq("ERROR: Procfile does not exist.\n")
end
end
describe "run" do
it "can run a command" do
expect(forked_foreman("run echo 1")).to eq("1\n")
end
it "doesn't parse options for the command" do
expect(forked_foreman("run grep -e FOO #{resource_path(".env")}")).to eq("FOO=bar\n")
end
it "includes the environment" do
expect(forked_foreman("run -e #{resource_path(".env")} #{resource_path("bin/env FOO")}")).to eq("bar\n")
end
it "can run a command from the Procfile" do
expect(forked_foreman("run -f #{resource_path("Procfile")} test")).to eq("testing\n")
end
it "exits with the same exit code as the command" do
expect(fork_and_get_exitstatus("run echo 1")).to eq(0)
expect(fork_and_get_exitstatus("run date 'invalid_date'")).to eq(1)
end
end
describe "version" do
it "displays gem version" do
expect(foreman("version").chomp).to eq(Foreman::VERSION)
end
it "displays gem version on shortcut command" do
expect(foreman("-v").chomp).to eq(Foreman::VERSION)
end
end
end
foreman-0.87.2/spec/foreman/export_spec.rb 0000644 0000041 0000041 00000001364 13765541231 020543 0 ustar www-data www-data require "spec_helper"
require "foreman/export"
describe "Foreman::Export" do
subject { Foreman::Export }
describe "with a formatter that doesn't declare the appropriate class" do
it "prints an error" do
expect(subject).to receive(:require).with("foreman/export/invalidformatter")
mock_export_error("Unknown export format: invalidformatter (no class Foreman::Export::Invalidformatter).") do
subject.formatter("invalidformatter")
end
end
end
describe "with an invalid formatter" do
it "prints an error" do
mock_export_error("Unknown export format: invalidformatter (unable to load file 'foreman/export/invalidformatter').") do
subject.formatter("invalidformatter")
end
end
end
end
foreman-0.87.2/spec/foreman/process_spec.rb 0000644 0000041 0000041 00000004323 13765541231 020676 0 ustar www-data www-data require 'spec_helper'
require 'foreman/process'
require 'ostruct'
require 'timeout'
require 'tmpdir'
describe Foreman::Process do
def run(process, options={})
rd, wr = IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
process.run(options.merge(:output => wr))
rd.gets
end
describe "#run" do
it "runs the process" do
process = Foreman::Process.new(resource_path("bin/test"))
expect(run(process)).to eq("testing\n")
end
it "can set environment" do
process = Foreman::Process.new(resource_path("bin/env FOO"), :env => { "FOO" => "bar" })
expect(run(process)).to eq("bar\n")
end
it "can set per-run environment" do
process = Foreman::Process.new(resource_path("bin/env FOO"))
expect(run(process, :env => { "FOO" => "bar "})).to eq("bar\n")
end
it "can handle env vars in the command" do
process = Foreman::Process.new(resource_path("bin/echo $FOO"), :env => { "FOO" => "bar" })
expect(run(process)).to eq("bar\n")
end
it "can handle per-run env vars in the command" do
process = Foreman::Process.new(resource_path("bin/echo $FOO"))
expect(run(process, :env => { "FOO" => "bar" })).to eq("bar\n")
end
it "should output utf8 properly" do
process = Foreman::Process.new(resource_path("bin/utf8"))
expect(run(process)).to eq(Foreman.ruby_18? ? "\xFF\x03\n" : "\xFF\x03\n".force_encoding('binary'))
end
it "can expand env in the command" do
process = Foreman::Process.new("command $FOO $BAR", :env => { "FOO" => "bar" })
expect(process.expanded_command).to eq("command bar $BAR")
end
it "can expand extra env in the command" do
process = Foreman::Process.new("command $FOO $BAR", :env => { "FOO" => "bar" })
expect(process.expanded_command("BAR" => "qux")).to eq("command bar qux")
end
it "can execute" do
expect(Kernel).to receive(:exec).with("bin/command")
process = Foreman::Process.new("bin/command")
process.exec
end
it "can execute with env" do
expect(Kernel).to receive(:exec).with("bin/command bar")
process = Foreman::Process.new("bin/command $FOO")
process.exec(:env => { "FOO" => "bar" })
end
end
end
foreman-0.87.2/spec/foreman/procfile_spec.rb 0000644 0000041 0000041 00000003127 13765541231 021024 0 ustar www-data www-data require 'spec_helper'
require 'foreman/procfile'
require 'pathname'
require 'tmpdir'
describe Foreman::Procfile, :fakefs do
subject { Foreman::Procfile.new }
it "can load from a file" do
write_procfile
subject.load "Procfile"
expect(subject["alpha"]).to eq("./alpha")
expect(subject["bravo"]).to eq("./bravo")
end
it "loads a passed-in Procfile" do
write_procfile
procfile = Foreman::Procfile.new("Procfile")
expect(procfile["alpha"]).to eq("./alpha")
expect(procfile["bravo"]).to eq("./bravo")
expect(procfile["foo-bar"]).to eq("./foo-bar")
expect(procfile["foo_bar"]).to eq("./foo_bar")
end
it 'only creates Procfile entries for lines matching regex' do
write_procfile
procfile = Foreman::Procfile.new("Procfile")
keys = procfile.instance_variable_get(:@entries).map(&:first)
expect(keys).to match_array(%w[alpha bravo foo-bar foo_bar])
end
it "returns nil when attempting to retrieve an non-existing entry" do
write_procfile
procfile = Foreman::Procfile.new("Procfile")
expect(procfile["unicorn"]).to eq(nil)
end
it "can have a process appended to it" do
subject["charlie"] = "./charlie"
expect(subject["charlie"]).to eq("./charlie")
end
it "can write to a string" do
subject["foo"] = "./foo"
subject["bar"] = "./bar"
expect(subject.to_s).to eq("foo: ./foo\nbar: ./bar")
end
it "can write to a file" do
subject["foo"] = "./foo"
subject["bar"] = "./bar"
Dir.mkdir('/tmp')
subject.save "/tmp/proc"
expect(File.read("/tmp/proc")).to eq("foo: ./foo\nbar: ./bar\n")
end
end
foreman-0.87.2/spec/spec_helper.rb 0000644 0000041 0000041 00000010016 13765541231 017044 0 ustar www-data www-data # it throws following message:
# W, [2018-02-06T18:16:06.360071 #72025] WARN -- : This usage of the Code Climate Test Reporter is now deprecated. Since version
# 1.0, we now require you to run `SimpleCov` in your test/spec helper, and then
# run the provided `codeclimate-test-reporter` binary separately to report your
# results to Code Climate.
#
# More information here: https://github.com/codeclimate/ruby-test-reporter/blob/master/README.md
# require "codeclimate-test-reporter"
# CodeClimate::TestReporter.start
require "simplecov"
SimpleCov.start do
add_filter "/spec/"
end
require "rspec"
require "timecop"
require "pp"
require "fakefs/safe"
require "fakefs/spec_helpers"
$:.unshift File.expand_path("../../lib", __FILE__)
def mock_export_error(message)
expect { yield }.to raise_error(Foreman::Export::Exception, message)
end
def mock_error(subject, message)
mock_exit do
expect(subject).to receive(:puts).with("ERROR: #{message}")
yield
end
end
def make_pipe
IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
end
def foreman(args)
capture_stdout do
begin
Foreman::CLI.start(args.split(" "))
rescue SystemExit
end
end
end
def forked_foreman(args)
rd, wr = make_pipe
Process.spawn("bundle exec bin/foreman #{args}", :out => wr, :err => wr)
wr.close
rd.read
end
def fork_and_capture(&blk)
rd, wr = make_pipe
pid = fork do
rd.close
wr.sync = true
$stdout.reopen wr
$stderr.reopen wr
blk.call
$stdout.flush
$stdout.close
end
wr.close
Process.wait pid
buffer = ""
until rd.eof?
buffer += rd.gets
end
end
def fork_and_get_exitstatus(args)
pid = Process.spawn("bundle exec bin/foreman #{args}", :out => "/dev/null", :err => "/dev/null")
Process.wait(pid)
$?.exitstatus
end
def mock_exit(&block)
expect(block).to raise_error(SystemExit)
end
def write_foreman_config(app)
File.open("/etc/foreman/#{app}.conf", "w") do |file|
file.puts %{#{app}_processes="alpha bravo"}
file.puts %{#{app}_alpha="1"}
file.puts %{#{app}_bravo="2"}
end
end
def write_procfile(procfile="Procfile", alpha_env="")
FileUtils.mkdir_p(File.dirname(procfile))
File.open(procfile, "w") do |file|
file.puts "alpha: ./alpha" + " #{alpha_env}".rstrip
file.puts "\n"
file.puts "bravo:\t./bravo"
file.puts "foo_bar:\t./foo_bar"
file.puts "foo-bar:\t./foo-bar"
file.puts "# baz:\t./baz"
end
File.expand_path(procfile)
end
def write_file(file)
FileUtils.mkdir_p(File.dirname(file))
File.open(file, 'w') do |f|
yield(f) if block_given?
end
end
def write_env(env=".env", options={"FOO"=>"bar"})
File.open(env, "w") do |file|
options.each do |key, val|
file.puts "#{key}=#{val}"
end
end
end
def without_fakefs
FakeFS.deactivate!
ret = yield
FakeFS.activate!
ret
end
def load_export_templates_into_fakefs(type)
without_fakefs do
Dir[File.expand_path("../../data/export/#{type}/**/*", __FILE__)].inject({}) do |hash, file|
next(hash) if File.directory?(file)
hash.update(file => File.read(file))
end
end.each do |filename, contents|
FileUtils.mkdir_p File.dirname(filename)
File.open(filename, "w") do |f|
f.puts contents
end
end
end
def resource_path(filename)
File.expand_path("../resources/#{filename}", __FILE__)
end
def example_export_file(filename)
FakeFS.deactivate!
data = File.read(File.expand_path(resource_path("export/#{filename}"), __FILE__))
FakeFS.activate!
data
end
def preserving_env
old_env = ENV.to_hash
begin
yield
ensure
ENV.clear
ENV.update(old_env)
end
end
def normalize_space(s)
s.gsub(/\n[\n\s]*/, "\n")
end
def capture_stdout
old_stdout = $stdout.dup
rd, wr = make_pipe
$stdout = wr
yield
wr.close
rd.read
ensure
$stdout = old_stdout
end
RSpec.configure do |config|
config.color = true
config.order = 'rand'
config.include FakeFS::SpecHelpers, :fakefs
config.before(:each) do
FileUtils.mkdir_p('/tmp')
end
config.after(:each) do
FileUtils.rm_rf('/tmp')
end
end
foreman-0.87.2/foreman.gemspec 0000644 0000041 0000041 00000016227 13765541231 016302 0 ustar www-data www-data #########################################################
# This file has been automatically generated by gem2tgz #
#########################################################
# -*- encoding: utf-8 -*-
# stub: foreman 0.87.2 ruby lib
Gem::Specification.new do |s|
s.name = "foreman".freeze
s.version = "0.87.2"
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
s.require_paths = ["lib".freeze]
s.authors = ["David Dollar".freeze]
s.date = "2020-08-07"
s.description = "Process manager for applications with multiple components".freeze
s.email = "ddollar@gmail.com".freeze
s.executables = ["foreman".freeze]
s.files = ["README.md".freeze, "bin/foreman".freeze, "bin/foreman-runner".freeze, "data/example/Procfile".freeze, "data/example/Procfile.without_colon".freeze, "data/example/error".freeze, "data/example/log/neverdie.log".freeze, "data/example/spawnee".freeze, "data/example/spawner".freeze, "data/example/ticker".freeze, "data/example/utf8".freeze, "data/export/bluepill/master.pill.erb".freeze, "data/export/daemon/master.conf.erb".freeze, "data/export/daemon/process.conf.erb".freeze, "data/export/daemon/process_master.conf.erb".freeze, "data/export/launchd/launchd.plist.erb".freeze, "data/export/runit/log/run.erb".freeze, "data/export/runit/run.erb".freeze, "data/export/supervisord/app.conf.erb".freeze, "data/export/systemd/master.target.erb".freeze, "data/export/systemd/process.service.erb".freeze, "data/export/upstart/master.conf.erb".freeze, "data/export/upstart/process.conf.erb".freeze, "data/export/upstart/process_master.conf.erb".freeze, "lib/foreman.rb".freeze, "lib/foreman/cli.rb".freeze, "lib/foreman/distribution.rb".freeze, "lib/foreman/engine.rb".freeze, "lib/foreman/engine/cli.rb".freeze, "lib/foreman/env.rb".freeze, "lib/foreman/export.rb".freeze, "lib/foreman/export/base.rb".freeze, "lib/foreman/export/bluepill.rb".freeze, "lib/foreman/export/daemon.rb".freeze, "lib/foreman/export/inittab.rb".freeze, "lib/foreman/export/launchd.rb".freeze, "lib/foreman/export/runit.rb".freeze, "lib/foreman/export/supervisord.rb".freeze, "lib/foreman/export/systemd.rb".freeze, "lib/foreman/export/upstart.rb".freeze, "lib/foreman/helpers.rb".freeze, "lib/foreman/process.rb".freeze, "lib/foreman/procfile.rb".freeze, "lib/foreman/vendor/thor/lib/thor.rb".freeze, "lib/foreman/vendor/thor/lib/thor/actions.rb".freeze, "lib/foreman/vendor/thor/lib/thor/actions/create_file.rb".freeze, "lib/foreman/vendor/thor/lib/thor/actions/create_link.rb".freeze, "lib/foreman/vendor/thor/lib/thor/actions/directory.rb".freeze, "lib/foreman/vendor/thor/lib/thor/actions/empty_directory.rb".freeze, "lib/foreman/vendor/thor/lib/thor/actions/file_manipulation.rb".freeze, "lib/foreman/vendor/thor/lib/thor/actions/inject_into_file.rb".freeze, "lib/foreman/vendor/thor/lib/thor/base.rb".freeze, "lib/foreman/vendor/thor/lib/thor/command.rb".freeze, "lib/foreman/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb".freeze, "lib/foreman/vendor/thor/lib/thor/core_ext/io_binary_read.rb".freeze, "lib/foreman/vendor/thor/lib/thor/core_ext/ordered_hash.rb".freeze, "lib/foreman/vendor/thor/lib/thor/error.rb".freeze, "lib/foreman/vendor/thor/lib/thor/group.rb".freeze, "lib/foreman/vendor/thor/lib/thor/invocation.rb".freeze, "lib/foreman/vendor/thor/lib/thor/line_editor.rb".freeze, "lib/foreman/vendor/thor/lib/thor/line_editor/basic.rb".freeze, "lib/foreman/vendor/thor/lib/thor/line_editor/readline.rb".freeze, "lib/foreman/vendor/thor/lib/thor/parser.rb".freeze, "lib/foreman/vendor/thor/lib/thor/parser/argument.rb".freeze, "lib/foreman/vendor/thor/lib/thor/parser/arguments.rb".freeze, "lib/foreman/vendor/thor/lib/thor/parser/option.rb".freeze, "lib/foreman/vendor/thor/lib/thor/parser/options.rb".freeze, "lib/foreman/vendor/thor/lib/thor/rake_compat.rb".freeze, "lib/foreman/vendor/thor/lib/thor/runner.rb".freeze, "lib/foreman/vendor/thor/lib/thor/shell.rb".freeze, "lib/foreman/vendor/thor/lib/thor/shell/basic.rb".freeze, "lib/foreman/vendor/thor/lib/thor/shell/color.rb".freeze, "lib/foreman/vendor/thor/lib/thor/shell/html.rb".freeze, "lib/foreman/vendor/thor/lib/thor/util.rb".freeze, "lib/foreman/vendor/thor/lib/thor/version.rb".freeze, "lib/foreman/version.rb".freeze, "man/foreman.1".freeze, "spec/foreman/cli_spec.rb".freeze, "spec/foreman/engine_spec.rb".freeze, "spec/foreman/export/base_spec.rb".freeze, "spec/foreman/export/bluepill_spec.rb".freeze, "spec/foreman/export/daemon_spec.rb".freeze, "spec/foreman/export/inittab_spec.rb".freeze, "spec/foreman/export/launchd_spec.rb".freeze, "spec/foreman/export/runit_spec.rb".freeze, "spec/foreman/export/supervisord_spec.rb".freeze, "spec/foreman/export/systemd_spec.rb".freeze, "spec/foreman/export/upstart_spec.rb".freeze, "spec/foreman/export_spec.rb".freeze, "spec/foreman/helpers_spec.rb".freeze, "spec/foreman/process_spec.rb".freeze, "spec/foreman/procfile_spec.rb".freeze, "spec/foreman_spec.rb".freeze, "spec/helper_spec.rb".freeze, "spec/resources/Procfile".freeze, "spec/resources/Procfile.bad".freeze, "spec/resources/bin/echo".freeze, "spec/resources/bin/env".freeze, "spec/resources/bin/test".freeze, "spec/resources/bin/utf8".freeze, "spec/resources/export/bluepill/app-concurrency.pill".freeze, "spec/resources/export/bluepill/app.pill".freeze, "spec/resources/export/daemon/app-alpha-1.conf".freeze, "spec/resources/export/daemon/app-alpha-2.conf".freeze, "spec/resources/export/daemon/app-alpha.conf".freeze, "spec/resources/export/daemon/app-bravo-1.conf".freeze, "spec/resources/export/daemon/app-bravo.conf".freeze, "spec/resources/export/daemon/app.conf".freeze, "spec/resources/export/inittab/inittab.concurrency".freeze, "spec/resources/export/inittab/inittab.default".freeze, "spec/resources/export/launchd/launchd-a.default".freeze, "spec/resources/export/launchd/launchd-b.default".freeze, "spec/resources/export/launchd/launchd-c.default".freeze, "spec/resources/export/runit/app-alpha-1/log/run".freeze, "spec/resources/export/runit/app-alpha-1/run".freeze, "spec/resources/export/runit/app-alpha-2/log/run".freeze, "spec/resources/export/runit/app-alpha-2/run".freeze, "spec/resources/export/runit/app-bravo-1/log/run".freeze, "spec/resources/export/runit/app-bravo-1/run".freeze, "spec/resources/export/supervisord/app-alpha-1.conf".freeze, "spec/resources/export/supervisord/app-alpha-2.conf".freeze, "spec/resources/export/systemd/app-alpha.1.service".freeze, "spec/resources/export/systemd/app-alpha.2.service".freeze, "spec/resources/export/systemd/app-alpha.target".freeze, "spec/resources/export/systemd/app-bravo.1.service".freeze, "spec/resources/export/systemd/app-bravo.target".freeze, "spec/resources/export/systemd/app.target".freeze, "spec/resources/export/upstart/app-alpha-1.conf".freeze, "spec/resources/export/upstart/app-alpha-2.conf".freeze, "spec/resources/export/upstart/app-alpha.conf".freeze, "spec/resources/export/upstart/app-bravo-1.conf".freeze, "spec/resources/export/upstart/app-bravo.conf".freeze, "spec/resources/export/upstart/app.conf".freeze, "spec/spec_helper.rb".freeze]
s.homepage = "http://github.com/ddollar/foreman".freeze
s.licenses = ["MIT".freeze]
s.rubygems_version = "2.5.2.1".freeze
s.summary = "Process manager for applications with multiple components".freeze
end
foreman-0.87.2/data/ 0000755 0000041 0000041 00000000000 13765541231 014207 5 ustar www-data www-data foreman-0.87.2/data/export/ 0000755 0000041 0000041 00000000000 13765541231 015530 5 ustar www-data www-data foreman-0.87.2/data/export/systemd/ 0000755 0000041 0000041 00000000000 13765541231 017220 5 ustar www-data www-data foreman-0.87.2/data/export/systemd/master.target.erb 0000644 0000041 0000041 00000000122 13765541231 022465 0 ustar www-data www-data [Unit]
Wants=<%= service_names.join(' ') %>
[Install]
WantedBy=multi-user.target
foreman-0.87.2/data/export/systemd/process.service.erb 0000644 0000041 0000041 00000001025 13765541231 023025 0 ustar www-data www-data [Unit]
PartOf=<%= app %>.target
StopWhenUnneeded=yes
[Service]
User=<%= user %>
WorkingDirectory=<%= engine.root %>
Environment=PORT=<%= port %>
Environment=PS=<%= process_name %>
<% engine.env.each_pair do |var,env| -%>
Environment="<%= var %>=<%= env %>"
<% end -%>
ExecStart=/bin/bash -lc 'exec -a "<%= app %>-<%= process_name %>" <%= process.command %>'
Restart=always
RestartSec=14s
StandardInput=null
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=%n
KillMode=mixed
TimeoutStopSec=<%= engine.options[:timeout] %>
foreman-0.87.2/data/export/supervisord/ 0000755 0000041 0000041 00000000000 13765541231 020115 5 ustar www-data www-data foreman-0.87.2/data/export/supervisord/app.conf.erb 0000644 0000041 0000041 00000001466 13765541231 022322 0 ustar www-data www-data <%
app_names = []
engine.each_process do |name, process|
1.upto(engine.formation[name]) do |num|
port = engine.port_for(process, num)
full_name = "#{app}-#{name}-#{num}"
environment = engine.env.merge("PORT" => port.to_s).map do |key, value|
value = shell_quote(value)
value = value.gsub('\=', '=')
value = value.gsub('\&', '&')
value = value.gsub('\?', '?')
"#{key}=\"#{value}\""
end
app_names << full_name
-%>
[program:<%= full_name %>]
command=<%= process.command %>
autostart=true
autorestart=true
stdout_logfile=<%= log %>/<%= name %>-<%= num %>.log
stderr_logfile=<%= log %>/<%= name %>-<%= num %>.error.log
user=<%= user %>
directory=<%= engine.root %>
environment=<%= environment.join(',') %>
<%
end
end
-%>
[group:<%= app %>]
programs=<%= app_names.join(',') %>
foreman-0.87.2/data/export/runit/ 0000755 0000041 0000041 00000000000 13765541231 016671 5 ustar www-data www-data foreman-0.87.2/data/export/runit/log/ 0000755 0000041 0000041 00000000000 13765541231 017452 5 ustar www-data www-data foreman-0.87.2/data/export/runit/log/run.erb 0000644 0000041 0000041 00000000247 13765541231 020753 0 ustar www-data www-data #!/bin/sh
set -e
LOG=<%= log %>/<%= name %>-<%= num %>
test -d "$LOG" || mkdir -p -m 2750 "$LOG" && chown <%= user %> "$LOG"
exec chpst -u <%= user %> svlogd "$LOG"
foreman-0.87.2/data/export/runit/run.erb 0000644 0000041 0000041 00000000226 13765541231 020167 0 ustar www-data www-data #!/bin/sh
cd <%= engine.root %>
exec 2>&1
exec chpst -u <%= user %> -e <%= File.join(location, "#{process_directory}/env") %> <%= process.command %>
foreman-0.87.2/data/export/upstart/ 0000755 0000041 0000041 00000000000 13765541231 017232 5 ustar www-data www-data foreman-0.87.2/data/export/upstart/process.conf.erb 0000644 0000041 0000041 00000000505 13765541231 022326 0 ustar www-data www-data start on starting <%= app %>-<%= name %>
stop on stopping <%= app %>-<%= name %>
respawn
env PORT=<%= port %>
<% engine.env.each do |name,value| -%>
<% next if name.upcase == "PORT" -%>
env <%= name %>='<%= value.gsub(/'/, "'\"'\"'") %>'
<% end -%>
setuid <%= user %>
chdir <%= engine.root %>
exec <%= process.command %>
foreman-0.87.2/data/export/upstart/process_master.conf.erb 0000644 0000041 0000041 00000000071 13765541231 023677 0 ustar www-data www-data start on starting <%= app %>
stop on stopping <%= app %>
foreman-0.87.2/data/export/upstart/master.conf.erb 0000644 0000041 0000041 00000000062 13765541231 022141 0 ustar www-data www-data start on runlevel [2345]
stop on runlevel [!2345]
foreman-0.87.2/data/export/launchd/ 0000755 0000041 0000041 00000000000 13765541231 017146 5 ustar www-data www-data foreman-0.87.2/data/export/launchd/launchd.plist.erb 0000644 0000041 0000041 00000002005 13765541231 022405 0 ustar www-data www-data
Label<%= "#{app}-#{name}-#{num}" %>EnvironmentVariables
<%- engine.env.merge("PORT" => port).each_pair do |var,env| -%>
<%= var %><%= env %>
<%- end -%>
ProgramArguments
<%- command_args.each do |command| -%>
<%= command %>
<%- end -%>
KeepAliveRunAtLoadStandardOutPath<%= log %>/<%= app %>-<%= name %>-<%=num%>.logStandardErrorPath<%= log %>/<%= app %>-<%= name %>-<%=num%>.logUserName<%= user %>WorkingDirectory<%= engine.root %>
foreman-0.87.2/data/export/daemon/ 0000755 0000041 0000041 00000000000 13765541231 016773 5 ustar www-data www-data foreman-0.87.2/data/export/daemon/process.conf.erb 0000644 0000041 0000041 00000000723 13765541231 022071 0 ustar www-data www-data 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 %>=<%= 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
foreman-0.87.2/data/export/daemon/process_master.conf.erb 0000644 0000041 0000041 00000000071 13765541231 023440 0 ustar www-data www-data start on starting <%= app %>
stop on stopping <%= app %>
foreman-0.87.2/data/export/daemon/master.conf.erb 0000644 0000041 0000041 00000000312 13765541231 021700 0 ustar www-data www-data 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]
foreman-0.87.2/data/export/bluepill/ 0000755 0000041 0000041 00000000000 13765541231 017340 5 ustar www-data www-data foreman-0.87.2/data/export/bluepill/master.pill.erb 0000644 0000041 0000041 00000001644 13765541231 022271 0 ustar www-data www-data Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
app.uid = "<%= user %>"
app.gid = "<%= user %>"
<% engine.each_process do |name, process| %>
<% 1.upto(engine.formation[name]) do |num| %>
<% port = engine.port_for(process, num) %>
app.process("<%= name %>-<%= num %>") do |process|
process.start_command = %Q{<%= process.command %>}
process.working_dir = "<%= engine.root %>"
process.daemonize = true
process.environment = <%= engine.env.merge("PORT" => port.to_s).inspect %>
process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
process.stop_grace_time = 45.seconds
process.stdout = process.stderr = "<%= log %>/<%= app %>-<%= name %>-<%= num %>.log"
process.monitor_children do |children|
children.stop_command "kill {{PID}}"
end
process.group = "<%= app %>-<%= name %>"
end
<% end %>
<% end %>
end
foreman-0.87.2/data/example/ 0000755 0000041 0000041 00000000000 13765541231 015642 5 ustar www-data www-data foreman-0.87.2/data/example/ticker 0000755 0000041 0000041 00000000355 13765541231 017054 0 ustar www-data www-data #!/usr/bin/env ruby
$stdout.sync = true
%w( SIGINT SIGTERM ).each do |signal|
trap(signal) do
puts "received #{signal} but i'm ignoring it!"
end
end
while true
puts "tick: #{ARGV.inspect} -- FOO:#{ENV["FOO"]}"
sleep 1
end
foreman-0.87.2/data/example/Procfile 0000644 0000041 0000041 00000000133 13765541231 017325 0 ustar www-data www-data ticker: ruby ./ticker $PORT
error: ruby ./error
utf8: ruby ./utf8
spawner: ./spawner
foreman-0.87.2/data/example/log/ 0000755 0000041 0000041 00000000000 13765541231 016423 5 ustar www-data www-data foreman-0.87.2/data/example/log/neverdie.log 0000644 0000041 0000041 00000000102 13765541231 020720 0 ustar www-data www-data tick
tick
./never_die:6:in `sleep': Interrupt
from ./never_die:6
foreman-0.87.2/data/example/utf8 0000755 0000041 0000041 00000000364 13765541231 016461 0 ustar www-data www-data #!/usr/bin/env ruby
# encoding: BINARY
$stdout.sync = true
while true
puts "\u65e5\u672c\u8a9e\u6587\u5b57\u5217"
puts "\u0915\u0932\u094d\u0907\u0928\u0643\u0637\u0628\u041a\u0430\u043b\u0438\u043d\u0430"
puts "\xff\x03"
sleep 1
end
foreman-0.87.2/data/example/spawnee 0000755 0000041 0000041 00000000216 13765541231 017231 0 ustar www-data www-data #!/bin/sh
NAME="$1"
sigterm() {
echo "$NAME: got sigterm"
}
#trap sigterm SIGTERM
while true; do
echo "$NAME: ping $$"
sleep 1
done
foreman-0.87.2/data/example/Procfile.without_colon 0000644 0000041 0000041 00000000044 13765541231 022222 0 ustar www-data www-data ticker ./ticker $PORT
error ./error foreman-0.87.2/data/example/error 0000755 0000041 0000041 00000000131 13765541231 016714 0 ustar www-data www-data #!/usr/bin/env ruby
$stdout.sync = true
puts "will error in 10s"
sleep 5
raise "Dying"
foreman-0.87.2/data/example/spawner 0000755 0000041 0000041 00000000073 13765541231 017247 0 ustar www-data www-data #!/bin/sh
./spawnee A &
./spawnee B &
./spawnee C &
wait
foreman-0.87.2/man/ 0000755 0000041 0000041 00000000000 13765541231 014051 5 ustar www-data www-data foreman-0.87.2/man/foreman.1 0000644 0000041 0000041 00000014777 13765541231 015602 0 ustar www-data www-data .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "FOREMAN" "1" "April 2020" "Foreman 0.87.2" "Foreman Manual"
.
.SH "NAME"
\fBforeman\fR \- manage Procfile\-based applications
.
.SH "SYNOPSIS"
\fBforeman start [process]\fR
.
.br
\fBforeman run \fR
.
.br
\fBforeman export [location]\fR
.
.SH "DESCRIPTION"
Foreman is a manager for Procfile\-based applications\. Its aim is to abstract away the details of the Procfile format, and allow you to either run your application directly or export it to some other process management format\.
.
.SH "RUNNING"
\fBforeman start\fR is used to run your application directly from the command line\.
.
.P
If no additional parameters are passed, foreman will run one instance of each type of process defined in your Procfile\.
.
.P
If a parameter is passed, foreman will run one instance of the specified application type\.
.
.P
The following options control how the application is run:
.
.TP
\fB\-m\fR, \fB\-\-formation\fR
Specify the number of each process type to run\. The value passed in should be in the format \fBprocess=num,process=num\fR
.
.TP
\fB\-e\fR, \fB\-\-env\fR
Specify one or more \.env files to load
.
.TP
\fB\-f\fR, \fB\-\-procfile\fR
Specify an alternate Procfile to load, implies \fB\-d\fR at the Procfile root\.
.
.TP
\fB\-p\fR, \fB\-\-port\fR
Specify which port to use as the base for this application\. Should be a multiple of 1000\.
.
.TP
\fB\-t\fR, \fB\-\-timeout\fR
Specify the amount of time (in seconds) processes have to shutdown gracefully before receiving a SIGKILL, defaults to 5\.
.
.P
\fBforeman run\fR is used to run one\-off commands using the same environment as your defined processes\.
.
.SH "EXPORTING"
\fBforeman export\fR is used to export your application to another process management format\.
.
.P
A location to export can be passed as an argument\. This argument may be either required or optional depending on the export format\.
.
.P
The following options control how the application is run:
.
.TP
\fB\-a\fR, \fB\-\-app\fR
Use this name rather than the application\'s root directory name as the name of the application when exporting\.
.
.TP
\fB\-m\fR, \fB\-\-formation\fR
Specify the number of each process type to run\. The value passed in should be in the format \fBprocess=num,process=num\fR
.
.TP
\fB\-l\fR, \fB\-\-log\fR
Specify the directory to place process logs in\.
.
.TP
\fB\-p\fR, \fB\-\-port\fR
Specify which port to use as the base for this application\. Should be a multiple of 1000\.
.
.TP
\fB\-t\fR, \fB\-\-template\fR
Specify an alternate template to use for creating export files\. See \fIhttps://github\.com/ddollar/foreman/tree/master/data/export\fR for examples\.
.
.TP
\fB\-u\fR, \fB\-\-user\fR
Specify the user the application should be run as\. Defaults to the app name
.
.SH "GLOBAL OPTIONS"
These options control all modes of foreman\'s operation\.
.
.TP
\fB\-d\fR, \fB\-\-root\fR
Specify an alternate application root\. This defaults to the directory containing the Procfile\.
.
.TP
\fB\-e\fR, \fB\-\-env\fR
Specify an alternate environment file\. You can specify more than one file by using: \fB\-\-env file1,file2\fR\.
.
.TP
\fB\-f\fR, \fB\-\-procfile\fR
Specify an alternate location for the application\'s Procfile\. This file\'s containing directory will be assumed to be the root directory of the application\.
.
.SH "EXPORT FORMATS"
foreman currently supports the following output formats:
.
.IP "\(bu" 4
bluepill
.
.IP "\(bu" 4
inittab
.
.IP "\(bu" 4
launchd
.
.IP "\(bu" 4
runit
.
.IP "\(bu" 4
supervisord
.
.IP "\(bu" 4
systemd
.
.IP "\(bu" 4
upstart
.
.IP "" 0
.
.SH "INITTAB EXPORT"
Will export a chunk of inittab\-compatible configuration:
.
.IP "" 4
.
.nf
# \-\-\-\-\- foreman example processes \-\-\-\-\-
EX01:4:respawn:/bin/su \- example \-c \'PORT=5000 bundle exec thin start >> /var/log/web\-1\.log 2>&1\'
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 \-\-\-\-\-
.
.fi
.
.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:
.
.P
\fBstart appname\fR
.
.P
\fBstop appname\-processname\fR
.
.P
\fBrestart appname\-processname\-3\fR
.
.SH "PROCFILE"
A Procfile should contain both a name for the process and the command used to run it\.
.
.IP "" 4
.
.nf
web: bundle exec thin start
job: bundle exec rake jobs:work
.
.fi
.
.IP "" 0
.
.P
A process name may contain letters, numbers and the underscore character\. You can validate your Procfile format using the \fBcheck\fR command:
.
.IP "" 4
.
.nf
$ foreman check
.
.fi
.
.IP "" 0
.
.P
The special environment variables \fB$PORT\fR and \fB$PS\fR are available within the Procfile\. \fB$PORT\fR is the port selected for that process\. \fB$PS\fR is the name of the process for the line\.
.
.P
The \fB$PORT\fR value starts as the base port as specified by \fB\-p\fR, then increments by 100 for each new process line\. Multiple instances of the same process are assigned \fB$PORT\fR values that increment by 1\.
.
.SH "ENVIRONMENT"
If a \fB\.env\fR file exists in the current directory, the default environment will be read from it\. This file should contain key/value pairs, separated by \fB=\fR, with one key/value pair per line\.
.
.IP "" 4
.
.nf
FOO=bar
BAZ=qux
.
.fi
.
.IP "" 0
.
.SH "DEFAULT OPTIONS"
If a \fB\.foreman\fR file exists in the current directory, default options will be read from it\. This file should be in YAML format with the long option name as keys\. Example:
.
.IP "" 4
.
.nf
formation: alpha=0,bravo=1
port: 15000
.
.fi
.
.IP "" 0
.
.SH "EXAMPLES"
Start one instance of each process type, interleave the output on stdout:
.
.IP "" 4
.
.nf
$ foreman start
.
.fi
.
.IP "" 0
.
.P
Export the application in upstart format:
.
.IP "" 4
.
.nf
$ foreman export upstart /etc/init
.
.fi
.
.IP "" 0
.
.P
Run one process type from the application defined in a specific Procfile:
.
.IP "" 4
.
.nf
$ foreman start alpha \-f ~/myapp/Procfile
.
.fi
.
.IP "" 0
.
.P
Start all processes except the one named worker:
.
.IP "" 4
.
.nf
$ foreman start \-m all=1,worker=0
.
.fi
.
.IP "" 0
.
.SH "COPYRIGHT"
Foreman is Copyright (C) 2010 David Dollar \fIhttp://daviddollar\.org\fR
foreman-0.87.2/lib/ 0000755 0000041 0000041 00000000000 13765541231 014044 5 ustar www-data www-data foreman-0.87.2/lib/foreman.rb 0000644 0000041 0000041 00000000463 13765541231 016023 0 ustar www-data www-data require "foreman/version"
module Foreman
def self.runner
File.expand_path("../../bin/foreman-runner", __FILE__)
end
def self.ruby_18?
defined?(RUBY_VERSION) and RUBY_VERSION =~ /^1\.8\.\d+/
end
def self.windows?
defined?(RUBY_PLATFORM) and RUBY_PLATFORM =~ /(win|w)32$/
end
end
foreman-0.87.2/lib/foreman/ 0000755 0000041 0000041 00000000000 13765541231 015473 5 ustar www-data www-data foreman-0.87.2/lib/foreman/version.rb 0000644 0000041 0000041 00000000052 13765541231 017502 0 ustar www-data www-data module Foreman
VERSION = "0.87.2"
end
foreman-0.87.2/lib/foreman/export/ 0000755 0000041 0000041 00000000000 13765541231 017014 5 ustar www-data www-data foreman-0.87.2/lib/foreman/export/runit.rb 0000644 0000041 0000041 00000001656 13765541231 020512 0 ustar www-data www-data require "erb"
require "foreman/export"
class Foreman::Export::Runit < Foreman::Export::Base
ENV_VARIABLE_REGEX = /([a-zA-Z_]+[a-zA-Z0-9_]*)=(\S+)/
def export
super
engine.each_process do |name, process|
1.upto(engine.formation[name]) do |num|
process_directory = "#{app}-#{name}-#{num}"
create_directory process_directory
create_directory "#{process_directory}/env"
create_directory "#{process_directory}/log"
write_template "runit/run.erb", "#{process_directory}/run", binding
chmod 0755, "#{process_directory}/run"
port = engine.port_for(process, num)
engine.env.merge("PORT" => port.to_s).each do |key, value|
write_file "#{process_directory}/env/#{key}", value
end
write_template "runit/log/run.erb", "#{process_directory}/log/run", binding
chmod 0755, "#{process_directory}/log/run"
end
end
end
end
foreman-0.87.2/lib/foreman/export/systemd.rb 0000644 0000041 0000041 00000001602 13765541231 021030 0 ustar www-data www-data require "erb"
require "foreman/export"
class Foreman::Export::Systemd < Foreman::Export::Base
def export
super
Dir["#{location}/#{app}*.target"]
.concat(Dir["#{location}/#{app}*.service"])
.concat(Dir["#{location}/#{app}*.target.wants/#{app}*.service"])
.each do |file|
clean file
end
Dir["#{location}/#{app}*.target.wants"].each do |file|
clean_dir file
end
service_names = []
engine.each_process do |name, process|
1.upto(engine.formation[name]) do |num|
port = engine.port_for(process, num)
process_name = "#{name}.#{num}"
service_filename = "#{app}-#{process_name}.service"
write_template "systemd/process.service.erb", service_filename, binding
service_names << service_filename
end
end
write_template "systemd/master.target.erb", "#{app}.target", binding
end
end
foreman-0.87.2/lib/foreman/export/bluepill.rb 0000644 0000041 0000041 00000000355 13765541231 021154 0 ustar www-data www-data require "erb"
require "foreman/export"
class Foreman::Export::Bluepill < Foreman::Export::Base
def export
super
clean "#{location}/#{app}.pill"
write_template "bluepill/master.pill.erb", "#{app}.pill", binding
end
end
foreman-0.87.2/lib/foreman/export/inittab.rb 0000644 0000041 0000041 00000002136 13765541231 020775 0 ustar www-data www-data require "foreman/export"
class Foreman::Export::Inittab < Foreman::Export::Base
def export
error("Must specify a location") unless location
inittab = []
inittab << "# ----- foreman #{app} processes -----"
index = 1
engine.each_process do |name, process|
1.upto(engine.formation[name]) do |num|
id = app.slice(0, 2).upcase + sprintf("%02d", index)
port = engine.port_for(process, num)
commands = []
commands << "cd #{engine.root}"
commands << "export PORT=#{port}"
engine.env.each_pair do |var, env|
commands << "export #{var.upcase}=#{shell_quote(env)}"
end
commands << "#{process.command} >> #{log}/#{name}-#{num}.log 2>&1"
inittab << "#{id}:4:respawn:/bin/su - #{user} -c '#{commands.join(";")}'"
index += 1
end
end
inittab << "# ----- end foreman #{app} processes -----"
inittab = inittab.join("\n") + "\n"
if location == "-"
puts inittab
else
say "writing: #{location}"
File.open(location, "w") { |file| file.puts inittab }
end
end
end
foreman-0.87.2/lib/foreman/export/base.rb 0000644 0000041 0000041 00000010466 13765541231 020262 0 ustar www-data www-data require "foreman/export"
require "ostruct"
require "pathname"
require "shellwords"
class Foreman::Export::Base
attr_reader :location
attr_reader :engine
attr_reader :options
attr_reader :formation
# deprecated
attr_reader :port
def initialize(location, engine, options={})
@location = location
@engine = engine
@options = options.dup
@formation = engine.formation
# deprecated
def port
Foreman::Export::Base.warn_deprecation!
engine.base_port
end
# deprecated
def template
Foreman::Export::Base.warn_deprecation!
options[:template]
end
# deprecated
def @engine.procfile
Foreman::Export::Base.warn_deprecation!
@processes.map do |process|
OpenStruct.new(
:name => @names[process],
:process => process
)
end
end
end
def export
error("Must specify a location") unless location
FileUtils.mkdir_p(location) rescue error("Could not create: #{location}")
chown user, log
chown user, run
end
def app
options[:app] || "app"
end
def log
options[:log] || "/var/log/#{app}"
end
def run
options[:run] || "/var/run/#{app}"
end
def user
options[:user] || app
end
private ######################################################################
def self.warn_deprecation!
@@deprecation_warned ||= false
return if @@deprecation_warned
puts "WARNING: Using deprecated exporter interface. Please update your exporter"
puts "the interface shown in the upstart exporter:"
puts
puts "https://github.com/ddollar/foreman/blob/master/lib/foreman/export/upstart.rb"
puts "https://github.com/ddollar/foreman/blob/master/data/export/upstart/process.conf.erb"
puts
@@deprecation_warned = true
end
def chown user, dir
FileUtils.chown user, nil, dir
rescue
error("Could not chown #{dir} to #{user}") unless File.writable?(dir) || ! File.exists?(dir)
end
def error(message)
raise Foreman::Export::Exception.new(message)
end
def say(message)
puts "[foreman export] %s" % message
end
def clean(filename)
return unless File.exists?(filename)
say "cleaning up: #{filename}"
FileUtils.rm(filename)
end
def clean_dir(dirname)
return unless File.exists?(dirname)
say "cleaning up directory: #{dirname}"
FileUtils.rm_r(dirname)
end
def shell_quote(value)
Shellwords.escape(value)
end
# deprecated
def old_export_template(exporter, file, template_root)
if template_root && File.exist?(file_path = File.join(template_root, file))
File.read(file_path)
elsif File.exist?(file_path = File.expand_path(File.join("~/.foreman/templates", file)))
File.read(file_path)
else
File.read(File.expand_path("../../../../data/export/#{exporter}/#{file}", __FILE__))
end
end
def export_template(name, file=nil, template_root=nil)
if file && template_root
old_export_template name, file, template_root
else
name_without_first = name.split("/")[1..-1].join("/")
matchers = []
matchers << File.join(options[:template], name_without_first) if options[:template]
matchers << File.expand_path("~/.foreman/templates/#{name}")
matchers << File.expand_path("../../../../data/export/#{name}", __FILE__)
File.read(matchers.detect { |m| File.exists?(m) })
end
end
def write_template(name, target, binding)
compiled = if ERB.instance_method(:initialize).parameters.assoc(:key) # Ruby 2.6+
ERB.new(export_template(name), trim_mode: '-').result(binding)
else
ERB.new(export_template(name), nil, '-').result(binding)
end
write_file target, compiled
end
def chmod(mode, file)
say "setting #{file} to mode #{mode}"
FileUtils.chmod mode, File.join(location, file)
end
def create_directory(dir)
say "creating: #{dir}"
FileUtils.mkdir_p(File.join(location, dir))
end
def create_symlink(link, target)
say "symlinking: #{link} -> #{target}"
FileUtils.symlink(target, File.join(location, link))
end
def write_file(filename, contents)
say "writing: #{filename}"
filename = File.join(location, filename) unless Pathname.new(filename).absolute?
File.open(filename, "w") do |file|
file.puts contents
end
end
end
foreman-0.87.2/lib/foreman/export/upstart.rb 0000644 0000041 0000041 00000002025 13765541231 021042 0 ustar www-data www-data require "erb"
require "foreman/export"
class Foreman::Export::Upstart < Foreman::Export::Base
def export
super
master_file = "#{app}.conf"
clean File.join(location, master_file)
write_template master_template, master_file, binding
engine.each_process do |name, process|
process_master_file = "#{app}-#{name}.conf"
process_file = "#{app}-#{name}-%s.conf"
Dir[
File.join(location, process_master_file),
File.join(location, process_file % "*")
].each { |f| clean(f) }
next if engine.formation[name] < 1
write_template process_master_template, process_master_file, binding
1.upto(engine.formation[name]) do |num|
port = engine.port_for(process, num)
write_template process_template, process_file % num, binding
end
end
end
private
def master_template
"upstart/master.conf.erb"
end
def process_master_template
"upstart/process_master.conf.erb"
end
def process_template
"upstart/process.conf.erb"
end
end
foreman-0.87.2/lib/foreman/export/supervisord.rb 0000644 0000041 0000041 00000000431 13765541231 021724 0 ustar www-data www-data require "erb"
require "foreman/export"
class Foreman::Export::Supervisord < Foreman::Export::Base
def export
super
Dir["#{location}/#{app}.conf"].each do |file|
clean file
end
write_template "supervisord/app.conf.erb", "#{app}.conf", binding
end
end
foreman-0.87.2/lib/foreman/export/daemon.rb 0000644 0000041 0000041 00000001506 13765541231 020606 0 ustar www-data www-data 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
foreman-0.87.2/lib/foreman/export/launchd.rb 0000644 0000041 0000041 00000001022 13765541231 020752 0 ustar www-data www-data require "erb"
require "foreman/export"
class Foreman::Export::Launchd < Foreman::Export::Base
def export
super
engine.each_process do |name, process|
1.upto(engine.formation[name]) do |num|
port = engine.port_for(process, num)
command_args = process.command.split(/\s+/).map{|arg|
case arg
when "$PORT" then port
else arg
end
}
write_template "launchd/launchd.plist.erb", "#{app}-#{name}-#{num}.plist", binding
end
end
end
end
foreman-0.87.2/lib/foreman/export.rb 0000644 0000041 0000041 00000001773 13765541231 017351 0 ustar www-data www-data require "foreman"
require "foreman/helpers"
require "pathname"
module Foreman::Export
extend Foreman::Helpers
class Exception < ::Exception; end
def self.formatter(format)
begin
require "foreman/export/#{ format.tr('-', '_') }"
classy_format = classify(format)
formatter = constantize("Foreman::Export::#{ classy_format }")
rescue NameError => ex
error "Unknown export format: #{format} (no class Foreman::Export::#{ classy_format })."
rescue LoadError => ex
error "Unknown export format: #{format} (unable to load file 'foreman/export/#{ format.tr('-', '_') }')."
end
end
def self.error(message)
raise Foreman::Export::Exception.new(message)
end
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"
foreman-0.87.2/lib/foreman/helpers.rb 0000644 0000041 0000041 00000002440 13765541231 017462 0 ustar www-data www-data module Foreman::Helpers
# Copied whole sale from, https://github.com/defunkt/resque/
# Given a word with dashes, returns a camel cased version of it.
#
# classify('job-name') # => 'JobName'
def classify(dashed_word)
dashed_word.split('-').each { |part| part[0] = part[0].chr.upcase }.join
end # Tries to find a constant with the name specified in the argument string:
#
# constantize("Module") # => Module
# constantize("Test::Unit") # => Test::Unit
#
# The name is assumed to be the one of a top-level constant, no matter
# whether it starts with "::" or not. No lexical context is taken into
# account:
#
# C = 'outside'
# module M
# C = 'inside'
# C # => 'inside'
# constantize("C") # => 'outside', same as ::C
# end
#
# NameError is raised when the constant is unknown.
def constantize(camel_cased_word)
camel_cased_word = camel_cased_word.to_s
names = camel_cased_word.split('::')
names.shift if names.empty? || names.first.empty?
constant = Object
names.each do |name|
args = Module.method(:const_get).arity != 1 ? [false] : []
if constant.const_defined?(name, *args)
constant = constant.const_get(name)
else
constant = constant.const_missing(name)
end
end
constant
end
end
foreman-0.87.2/lib/foreman/procfile.rb 0000644 0000041 0000041 00000003562 13765541231 017631 0 ustar www-data www-data require "foreman"
# Reads and writes Procfiles
#
# A valid Procfile entry is captured by this regex:
#
# /^([A-Za-z0-9_]+):\s*(.+)$/
#
# All other lines are ignored.
#
class Foreman::Procfile
# Initialize a Procfile
#
# @param [String] filename (nil) An optional filename to read from
#
def initialize(filename=nil)
@entries = []
load(filename) if filename
end
# Yield each +Procfile+ entry in order
#
def entries
@entries.each do |(name, command)|
yield name, command
end
end
# Retrieve a +Procfile+ command by name
#
# @param [String] name The name of the Procfile entry to retrieve
#
def [](name)
if entry = @entries.detect { |n,c| name == n }
entry.last
end
end
# Create a +Procfile+ entry
#
# @param [String] name The name of the +Procfile+ entry to create
# @param [String] command The command of the +Procfile+ entry to create
#
def []=(name, command)
delete name
@entries << [name, command]
end
# Remove a +Procfile+ entry
#
# @param [String] name The name of the +Procfile+ entry to remove
#
def delete(name)
@entries.reject! { |n,c| name == n }
end
# Load a Procfile from a file
#
# @param [String] filename The filename of the +Procfile+ to load
#
def load(filename)
@entries.replace parse(filename)
end
# Save a Procfile to a file
#
# @param [String] filename Save the +Procfile+ to this file
#
def save(filename)
File.open(filename, 'w') do |file|
file.puts self.to_s
end
end
# Get the +Procfile+ as a +String+
#
def to_s
@entries.map do |name, command|
[ name, command ].join(": ")
end.join("\n")
end
private
def parse(filename)
File.read(filename).gsub("\r\n","\n").split("\n").map do |line|
if line =~ /^([A-Za-z0-9_-]+):\s*(.+)$/
[$1, $2]
end
end.compact
end
end
foreman-0.87.2/lib/foreman/engine.rb 0000644 0000041 0000041 00000027730 13765541231 017276 0 ustar www-data www-data require "foreman"
require "foreman/env"
require "foreman/process"
require "foreman/procfile"
require "tempfile"
require "fileutils"
require "thread"
class Foreman::Engine
# The signals that the engine cares about.
#
HANDLED_SIGNALS = [ :TERM, :INT, :HUP, :USR1, :USR2 ]
attr_reader :env
attr_reader :options
attr_reader :processes
# Create an +Engine+ for running processes
#
# @param [Hash] options
#
# @option options [String] :formation (all=1) The process formation to use
# @option options [Fixnum] :port (5000) The base port to assign to processes
# @option options [String] :root (Dir.pwd) The root directory from which to run processes
#
def initialize(options={})
@options = options.dup
@options[:formation] ||= "all=1"
@options[:timeout] ||= 5
@env = {}
@mutex = Mutex.new
@names = {}
@processes = []
@running = {}
@readers = {}
@shutdown = false
# Self-pipe for deferred signal-handling (ala djb: http://cr.yp.to/docs/selfpipe.html)
reader, writer = create_pipe
reader.close_on_exec = true if reader.respond_to?(:close_on_exec)
writer.close_on_exec = true if writer.respond_to?(:close_on_exec)
@selfpipe = { :reader => reader, :writer => writer }
# Set up a global signal queue
# http://blog.rubybestpractices.com/posts/ewong/016-Implementing-Signal-Handlers.html
Thread.main[:signal_queue] = []
end
# Start the processes registered to this +Engine+
#
def start
register_signal_handlers
startup
spawn_processes
watch_for_output
sleep 0.1
wait_for_shutdown_or_child_termination
shutdown
exit(@exitstatus) if @exitstatus
end
# Set up deferred signal handlers
#
def register_signal_handlers
HANDLED_SIGNALS.each do |sig|
if ::Signal.list.include? sig.to_s
trap(sig) { Thread.main[:signal_queue] << sig ; notice_signal }
end
end
end
# Unregister deferred signal handlers
#
def restore_default_signal_handlers
HANDLED_SIGNALS.each do |sig|
trap(sig, :DEFAULT) if ::Signal.list.include? sig.to_s
end
end
# Wake the main thread up via the selfpipe when there's a signal
#
def notice_signal
@selfpipe[:writer].write_nonblock( '.' )
rescue Errno::EAGAIN
# Ignore writes that would block
rescue Errno::EINTR
# Retry if another signal arrived while writing
retry
end
# Invoke the real handler for signal +sig+. This shouldn't be called directly
# by signal handlers, as it might invoke code which isn't re-entrant.
#
# @param [Symbol] sig the name of the signal to be handled
#
def handle_signal(sig)
case sig
when :TERM
handle_term_signal
when :INT
handle_interrupt
when :HUP
handle_hangup
when *HANDLED_SIGNALS
handle_signal_forward(sig)
else
system "unhandled signal #{sig}"
end
end
# Handle a TERM signal
#
def handle_term_signal
system "SIGTERM received, starting shutdown"
@shutdown = true
end
# Handle an INT signal
#
def handle_interrupt
system "SIGINT received, starting shutdown"
@shutdown = true
end
# Handle a HUP signal
#
def handle_hangup
system "SIGHUP received, starting shutdown"
@shutdown = true
end
def handle_signal_forward(signal)
system "#{signal} received, forwarding it to children"
kill_children signal
end
# Register a process to be run by this +Engine+
#
# @param [String] name A name for this process
# @param [String] command The command to run
# @param [Hash] options
#
# @option options [Hash] :env A custom environment for this process
#
def register(name, command, options={})
options[:env] ||= env
options[:cwd] ||= File.dirname(command.split(" ").first)
process = Foreman::Process.new(command, options)
@names[process] = name
@processes << process
end
# Clear the processes registered to this +Engine+
#
def clear
@names = {}
@processes = []
end
# Register processes by reading a Procfile
#
# @param [String] filename A Procfile from which to read processes to register
#
def load_procfile(filename)
options[:root] ||= File.dirname(filename)
Foreman::Procfile.new(filename).entries do |name, command|
register name, command, :cwd => options[:root]
end
self
end
# Load a .env file into the +env+ for this +Engine+
#
# @param [String] filename A .env file to load into the environment
#
def load_env(filename)
Foreman::Env.new(filename).entries do |name, value|
@env[name] = value
end
end
# Send a signal to all processes started by this +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
begin
pids = @running.keys.compact
Process.kill signal, *pids unless pids.empty?
rescue Errno::ESRCH, Errno::EPERM
end
end
end
# Send a signal to the whole process group.
#
# @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
end
# Get the process formation
#
# @returns [Fixnum] The formation count for the specified process
#
def formation
@formation ||= parse_formation(options[:formation])
end
# List the available process names
#
# @returns [Array] A list of process names
#
def process_names
@processes.map { |p| @names[p] }
end
# Get the +Process+ for a specifid name
#
# @param [String] name The process name
#
# @returns [Foreman::Process] The +Process+ for the specified name
#
def process(name)
@names.invert[name]
end
# Yield each +Process+ in order
#
def each_process
process_names.each do |name|
yield name, process(name)
end
end
# Get the root directory for this +Engine+
#
# @returns [String] The root directory
#
def root
File.expand_path(options[:root] || Dir.pwd)
end
# Get the port for a given process and offset
#
# @param [Foreman::Process] process A +Process+ associated with this engine
# @param [Fixnum] instance The instance of the process
#
# @returns [Fixnum] port The port to use for this instance of this process
#
def port_for(process, instance, base=nil)
if base
base + (@processes.index(process.process) * 100) + (instance - 1)
else
base_port + (@processes.index(process) * 100) + (instance - 1)
end
end
# Get the base port for this foreman instance
#
# @returns [Fixnum] port The base port
#
def base_port
(options[:port] || env["PORT"] || ENV["PORT"] || 5000).to_i
end
# deprecated
def environment
env
end
private
### Engine API ######################################################
def startup
raise TypeError, "must use a subclass of Foreman::Engine"
end
def output(name, data)
raise TypeError, "must use a subclass of Foreman::Engine"
end
def shutdown
raise TypeError, "must use a subclass of Foreman::Engine"
end
## Helpers ##########################################################
def create_pipe
IO.method(:pipe).arity.zero? ? IO.pipe : IO.pipe("BINARY")
end
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
def parse_formation(formation)
pairs = formation.to_s.gsub(/\s/, "").split(",")
pairs.inject(Hash.new(0)) do |ax, pair|
process, amount = pair.split("=")
process == "all" ? ax.default = amount.to_i : ax[process] = amount.to_i
ax
end
end
def output_with_mutex(name, message)
@mutex.synchronize do
output name, message
end
end
def system(message)
output_with_mutex "system", message
end
def termination_message_for(status)
if status.exited?
"exited with code #{status.exitstatus}"
elsif status.signaled?
"terminated by SIG#{Signal.list.invert[status.termsig]}"
else
"died a mysterious death"
end
end
def flush_reader(reader)
until reader.eof?
data = reader.gets
output_with_mutex name_for(@readers.key(reader)), data
end
end
## Engine ###########################################################
def spawn_processes
@processes.each do |process|
1.upto(formation[@names[process]]) do |n|
reader, writer = create_pipe
begin
pid = process.run(:output => writer, :env => {
"PORT" => port_for(process, n).to_s,
"PS" => name_for_index(process, n)
})
writer.puts "started with pid #{pid}"
rescue Errno::ENOENT
writer.puts "unknown command: #{process.command}"
end
@running[pid] = [process, n]
@readers[pid] = reader
end
end
end
def read_self_pipe
@selfpipe[:reader].read_nonblock(11)
rescue Errno::EAGAIN, Errno::EINTR, Errno::EBADF, Errno::EWOULDBLOCK
# ignore
end
def handle_signals
while sig = Thread.main[:signal_queue].shift
self.handle_signal(sig)
end
end
def handle_io(readers)
readers.each do |reader|
next if reader == @selfpipe[:reader]
if reader.eof?
@readers.delete_if { |key, value| value == reader }
else
data = reader.gets
output_with_mutex name_for(@readers.invert[reader]), data
end
end
end
def watch_for_output
Thread.new do
begin
loop do
io = IO.select([@selfpipe[:reader]] + @readers.values, nil, nil, 30)
read_self_pipe
handle_signals
handle_io(io ? io.first : [])
end
rescue Exception => ex
puts ex.message
puts ex.backtrace
end
end
end
def wait_for_shutdown_or_child_termination
loop do
# Stop if it is time to shut down (asked via a signal)
break if @shutdown
# Stop if any of the children died
break if check_for_termination
# Sleep for a moment and do not blow up if any signals are coming our way
begin
sleep(1)
rescue Exception
# noop
end
end
# Ok, we have exited from the main loop, time to shut down gracefully
terminate_gracefully
end
def check_for_termination
# Check if any of the children have died off
pid, status = begin
Process.wait2(-1, Process::WNOHANG)
rescue Errno::ECHILD
return nil
end
# record the exit status
@exitstatus ||= status.exitstatus if status
# If no childred have died, nothing to do here
return nil unless pid
# Log the information about the process that exited
output_with_mutex name_for(pid), termination_message_for(status)
# Delete it from the list of running processes and return its pid
@running.delete(pid)
return pid
end
def terminate_gracefully
restore_default_signal_handlers
# Tell all children to stop gracefully
if Foreman.windows?
system "sending SIGKILL to all processes"
kill_children "SIGKILL"
else
system "sending SIGTERM to all processes"
kill_children "SIGTERM"
end
# Wait for all children to stop or until the time comes to kill them all
start_time = Time.now
while Time.now - start_time <= options[:timeout]
return if @running.empty?
check_for_termination
# Sleep for a moment and do not blow up if more signals are coming our way
begin
sleep(0.1)
rescue Exception
# noop
end
end
# Ok, we have no other option than to kill all of our children
system "sending SIGKILL to all processes"
kill_children "SIGKILL"
end
end
foreman-0.87.2/lib/foreman/vendor/ 0000755 0000041 0000041 00000000000 13765541231 016770 5 ustar www-data www-data foreman-0.87.2/lib/foreman/vendor/thor/ 0000755 0000041 0000041 00000000000 13765541231 017744 5 ustar www-data www-data foreman-0.87.2/lib/foreman/vendor/thor/lib/ 0000755 0000041 0000041 00000000000 13765541231 020512 5 ustar www-data www-data foreman-0.87.2/lib/foreman/vendor/thor/lib/thor.rb 0000644 0000041 0000041 00000036416 13765541231 022025 0 ustar www-data www-data require "set"
require "foreman/vendor/thor/lib/thor/base"
class Foreman::Thor
class << self
# Allows for custom "Command" package naming.
#
# === Parameters
# name
# options
#
def package_name(name, _ = {})
@package_name = name.nil? || name == "" ? nil : name
end
# Sets the default command when thor is executed without an explicit command to be called.
#
# ==== Parameters
# meth:: name of the default command
#
def default_command(meth = nil)
if meth
@default_command = meth == :none ? "help" : meth.to_s
else
@default_command ||= from_superclass(:default_command, "help")
end
end
alias_method :default_task, :default_command
# Registers another Foreman::Thor subclass as a command.
#
# ==== Parameters
# klass:: Foreman::Thor subclass to register
# command:: Subcommand name to use
# usage:: Short usage for the subcommand
# description:: Description for the subcommand
def register(klass, subcommand_name, usage, description, options = {})
if klass <= Foreman::Thor::Group
desc usage, description, options
define_method(subcommand_name) { |*args| invoke(klass, args) }
else
desc usage, description, options
subcommand subcommand_name, klass
end
end
# Defines the usage and the description of the next command.
#
# ==== Parameters
# usage
# description
# options
#
def desc(usage, description, options = {})
if options[:for]
command = find_and_refresh_command(options[:for])
command.usage = usage if usage
command.description = description if description
else
@usage = usage
@desc = description
@hide = options[:hide] || false
end
end
# Defines the long description of the next command.
#
# ==== Parameters
# long description
#
def long_desc(long_description, options = {})
if options[:for]
command = find_and_refresh_command(options[:for])
command.long_description = long_description if long_description
else
@long_desc = long_description
end
end
# Maps an input to a command. If you define:
#
# map "-T" => "list"
#
# Running:
#
# thor -T
#
# Will invoke the list command.
#
# ==== Parameters
# Hash[String|Array => Symbol]:: Maps the string or the strings in the array to the given command.
#
def map(mappings = nil)
@map ||= from_superclass(:map, {})
if mappings
mappings.each do |key, value|
if key.respond_to?(:each)
key.each { |subkey| @map[subkey] = value }
else
@map[key] = value
end
end
end
@map
end
# Declares the options for the next command to be declared.
#
# ==== Parameters
# Hash[Symbol => Object]:: The hash key is the name of the option and the value
# is the type of the option. Can be :string, :array, :hash, :boolean, :numeric
# or :required (string). If you give a value, the type of the value is used.
#
def method_options(options = nil)
@method_options ||= {}
build_options(options, @method_options) if options
@method_options
end
alias_method :options, :method_options
# Adds an option to the set of method options. If :for is given as option,
# it allows you to change the options from a previous defined command.
#
# def previous_command
# # magic
# end
#
# method_option :foo => :bar, :for => :previous_command
#
# def next_command
# # magic
# end
#
# ==== Parameters
# name:: The name of the argument.
# options:: Described below.
#
# ==== Options
# :desc - Description for the argument.
# :required - If the argument is required or not.
# :default - Default value for this argument. It cannot be required and have default values.
# :aliases - Aliases for this option.
# :type - The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
# :banner - String to show on usage notes.
# :hide - If you want to hide this option from the help.
#
def method_option(name, options = {})
scope = if options[:for]
find_and_refresh_command(options[:for]).options
else
method_options
end
build_option(name, options, scope)
end
alias_method :option, :method_option
def disable_class_options
@disable_class_options = true
end
# Prints help information for the given command.
#
# ==== Parameters
# shell
# command_name
#
def command_help(shell, command_name)
meth = normalize_command_name(command_name)
command = all_commands[meth]
handle_no_command_error(meth) unless command
shell.say "Usage:"
shell.say " #{banner(command)}"
shell.say
class_options_help(shell, nil => command.options.values)
if command.long_description
shell.say "Description:"
shell.print_wrapped(command.long_description, :indent => 2)
else
shell.say command.description
end
end
alias_method :task_help, :command_help
# Prints help information for this class.
#
# ==== Parameters
# shell
#
def help(shell, subcommand = false)
list = printable_commands(true, subcommand)
Foreman::Thor::Util.thor_classes_in(self).each do |klass|
list += klass.printable_commands(false)
end
list.sort! { |a, b| a[0] <=> b[0] }
if defined?(@package_name) && @package_name
shell.say "#{@package_name} commands:"
else
shell.say "Commands:"
end
shell.print_table(list, :indent => 2, :truncate => true)
shell.say
class_options_help(shell)
end
# Returns commands ready to be printed.
def printable_commands(all = true, subcommand = false)
(all ? all_commands : commands).map do |_, command|
next if command.hidden?
item = []
item << banner(command, false, subcommand)
item << (command.description ? "# #{command.description.gsub(/\s+/m, ' ')}" : "")
item
end.compact
end
alias_method :printable_tasks, :printable_commands
def subcommands
@subcommands ||= from_superclass(:subcommands, [])
end
alias_method :subtasks, :subcommands
def subcommand_classes
@subcommand_classes ||= {}
end
def subcommand(subcommand, subcommand_class)
subcommands << subcommand.to_s
subcommand_class.subcommand_help subcommand
subcommand_classes[subcommand.to_s] = subcommand_class
define_method(subcommand) do |*args|
args, opts = Foreman::Thor::Arguments.split(args)
invoke_args = [args, opts, {:invoked_via_subcommand => true, :class_options => options}]
invoke_args.unshift "help" if opts.delete("--help") || opts.delete("-h")
invoke subcommand_class, *invoke_args
end
end
alias_method :subtask, :subcommand
# Extend check unknown options to accept a hash of conditions.
#
# === Parameters
# options: A hash containing :only and/or :except keys
def check_unknown_options!(options = {})
@check_unknown_options ||= {}
options.each do |key, value|
if value
@check_unknown_options[key] = Array(value)
else
@check_unknown_options.delete(key)
end
end
@check_unknown_options
end
# Overwrite check_unknown_options? to take subcommands and options into account.
def check_unknown_options?(config) #:nodoc:
options = check_unknown_options
return false unless options
command = config[:current_command]
return true unless command
name = command.name
if subcommands.include?(name)
false
elsif options[:except]
!options[:except].include?(name.to_sym)
elsif options[:only]
options[:only].include?(name.to_sym)
else
true
end
end
# Stop parsing of options as soon as an unknown option or a regular
# argument is encountered. All remaining arguments are passed to the command.
# This is useful if you have a command that can receive arbitrary additional
# options, and where those additional options should not be handled by
# Foreman::Thor.
#
# ==== Example
#
# To better understand how this is useful, let's consider a command that calls
# an external command. A user may want to pass arbitrary options and
# arguments to that command. The command itself also accepts some options,
# which should be handled by Foreman::Thor.
#
# class_option "verbose", :type => :boolean
# stop_on_unknown_option! :exec
# check_unknown_options! :except => :exec
#
# desc "exec", "Run a shell command"
# def exec(*args)
# puts "diagnostic output" if options[:verbose]
# Kernel.exec(*args)
# end
#
# Here +exec+ can be called with +--verbose+ to get diagnostic output,
# e.g.:
#
# $ thor exec --verbose echo foo
# diagnostic output
# foo
#
# But if +--verbose+ is given after +echo+, it is passed to +echo+ instead:
#
# $ thor exec echo --verbose foo
# --verbose foo
#
# ==== Parameters
# Symbol ...:: A list of commands that should be affected.
def stop_on_unknown_option!(*command_names)
stop_on_unknown_option.merge(command_names)
end
def stop_on_unknown_option?(command) #:nodoc:
command && stop_on_unknown_option.include?(command.name.to_sym)
end
protected
def stop_on_unknown_option #:nodoc:
@stop_on_unknown_option ||= Set.new
end
# The method responsible for dispatching given the args.
def dispatch(meth, given_args, given_opts, config) #:nodoc: # rubocop:disable MethodLength
meth ||= retrieve_command_name(given_args)
command = all_commands[normalize_command_name(meth)]
if !command && config[:invoked_via_subcommand]
# We're a subcommand and our first argument didn't match any of our
# commands. So we put it back and call our default command.
given_args.unshift(meth)
command = all_commands[normalize_command_name(default_command)]
end
if command
args, opts = Foreman::Thor::Options.split(given_args)
if stop_on_unknown_option?(command) && !args.empty?
# given_args starts with a non-option, so we treat everything as
# ordinary arguments
args.concat opts
opts.clear
end
else
args = given_args
opts = nil
command = dynamic_command_class.new(meth)
end
opts = given_opts || opts || []
config[:current_command] = command
config[:command_options] = command.options
instance = new(args, opts, config)
yield instance if block_given?
args = instance.args
trailing = args[Range.new(arguments.size, -1)]
instance.invoke_command(command, trailing || [])
end
# The banner for this class. You can customize it if you are invoking the
# thor class by another ways which is not the Foreman::Thor::Runner. It receives
# the command that is going to be invoked and a boolean which indicates if
# the namespace should be displayed as arguments.
#
def banner(command, namespace = nil, subcommand = false)
"#{basename} #{command.formatted_usage(self, $thor_runner, subcommand)}"
end
def baseclass #:nodoc:
Foreman::Thor
end
def dynamic_command_class #:nodoc:
Foreman::Thor::DynamicCommand
end
def create_command(meth) #:nodoc:
@usage ||= nil
@desc ||= nil
@long_desc ||= nil
@disable_class_options ||= nil
if @usage && @desc
base_class = @hide ? Foreman::Thor::HiddenCommand : Foreman::Thor::Command
commands[meth] = base_class.new(meth, @desc, @long_desc, @usage, method_options, @disable_class_options)
@usage, @desc, @long_desc, @method_options, @hide, @disable_class_options = nil
true
elsif all_commands[meth] || meth == "method_missing"
true
else
puts "[WARNING] Attempted to create command #{meth.inspect} without usage or description. " \
"Call desc if you want this method to be available as command or declare it inside a " \
"no_commands{} block. Invoked from #{caller[1].inspect}."
false
end
end
alias_method :create_task, :create_command
def initialize_added #:nodoc:
class_options.merge!(method_options)
@method_options = nil
end
# Retrieve the command name from given args.
def retrieve_command_name(args) #:nodoc:
meth = args.first.to_s unless args.empty?
args.shift if meth && (map[meth] || meth !~ /^\-/)
end
alias_method :retrieve_task_name, :retrieve_command_name
# receives a (possibly nil) command name and returns a name that is in
# the commands hash. In addition to normalizing aliases, this logic
# will determine if a shortened command is an unambiguous substring of
# a command or alias.
#
# +normalize_command_name+ also converts names like +animal-prison+
# into +animal_prison+.
def normalize_command_name(meth) #:nodoc:
return default_command.to_s.tr("-", "_") unless meth
possibilities = find_command_possibilities(meth)
raise AmbiguousTaskError, "Ambiguous command #{meth} matches [#{possibilities.join(', ')}]" if possibilities.size > 1
if possibilities.empty?
meth ||= default_command
elsif map[meth]
meth = map[meth]
else
meth = possibilities.first
end
meth.to_s.tr("-", "_") # treat foo-bar as foo_bar
end
alias_method :normalize_task_name, :normalize_command_name
# this is the logic that takes the command name passed in by the user
# and determines whether it is an unambiguous substrings of a command or
# alias name.
def find_command_possibilities(meth)
len = meth.to_s.length
possibilities = all_commands.merge(map).keys.select { |n| meth == n[0, len] }.sort
unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
if possibilities.include?(meth)
[meth]
elsif unique_possibilities.size == 1
unique_possibilities
else
possibilities
end
end
alias_method :find_task_possibilities, :find_command_possibilities
def subcommand_help(cmd)
desc "help [COMMAND]", "Describe subcommands or one specific subcommand"
class_eval "
def help(command = nil, subcommand = true); super; end
"
end
alias_method :subtask_help, :subcommand_help
end
include Foreman::Thor::Base
map HELP_MAPPINGS => :help
desc "help [COMMAND]", "Describe available commands or one specific command"
disable_class_options
def help(command = nil, subcommand = false)
if command
if self.class.subcommands.include? command
self.class.subcommand_classes[command].help(shell, true)
else
self.class.command_help(shell, command)
end
else
self.class.help(shell, subcommand)
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/ 0000755 0000041 0000041 00000000000 13765541231 021466 5 ustar www-data www-data foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/core_ext/ 0000755 0000041 0000041 00000000000 13765541231 023276 5 ustar www-data www-data foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb 0000644 0000041 0000041 00000003615 13765541231 031504 0 ustar www-data www-data class Foreman::Thor
module CoreExt #:nodoc:
# A hash with indifferent access and magic predicates.
#
# hash = Foreman::Thor::CoreExt::HashWithIndifferentAccess.new 'foo' => 'bar', 'baz' => 'bee', 'force' => true
#
# hash[:foo] #=> 'bar'
# hash['foo'] #=> 'bar'
# hash.foo? #=> true
#
class HashWithIndifferentAccess < ::Hash #:nodoc:
def initialize(hash = {})
super()
hash.each do |key, value|
self[convert_key(key)] = value
end
end
def [](key)
super(convert_key(key))
end
def []=(key, value)
super(convert_key(key), value)
end
def delete(key)
super(convert_key(key))
end
def fetch(key, *args)
super(convert_key(key), *args)
end
def key?(key)
super(convert_key(key))
end
def values_at(*indices)
indices.map { |key| self[convert_key(key)] }
end
def merge(other)
dup.merge!(other)
end
def merge!(other)
other.each do |key, value|
self[convert_key(key)] = value
end
self
end
# Convert to a Hash with String keys.
def to_hash
Hash.new(default).merge!(self)
end
protected
def convert_key(key)
key.is_a?(Symbol) ? key.to_s : key
end
# Magic predicates. For instance:
#
# options.force? # => !!options['force']
# options.shebang # => "/usr/lib/local/ruby"
# options.test_framework?(:rspec) # => options[:test_framework] == :rspec
#
def method_missing(method, *args)
method = method.to_s
if method =~ /^(\w+)\?$/
if args.empty?
!!self[$1]
else
self[$1] == args.first
end
else
self[method]
end
end
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/core_ext/ordered_hash.rb 0000644 0000041 0000041 00000005136 13765541231 026257 0 ustar www-data www-data class Foreman::Thor
module CoreExt
class OrderedHash < ::Hash
if RUBY_VERSION < "1.9"
def initialize(*args, &block)
super
@keys = []
end
def initialize_copy(other)
super
# make a deep copy of keys
@keys = other.keys
end
def []=(key, value)
@keys << key unless key?(key)
super
end
def delete(key)
if key? key
index = @keys.index(key)
@keys.delete_at index
end
super
end
def delete_if
super
sync_keys!
self
end
alias_method :reject!, :delete_if
def reject(&block)
dup.reject!(&block)
end
def keys
@keys.dup
end
def values
@keys.map { |key| self[key] }
end
def to_hash
self
end
def to_a
@keys.map { |key| [key, self[key]] }
end
def each_key
return to_enum(:each_key) unless block_given?
@keys.each { |key| yield(key) }
self
end
def each_value
return to_enum(:each_value) unless block_given?
@keys.each { |key| yield(self[key]) }
self
end
def each
return to_enum(:each) unless block_given?
@keys.each { |key| yield([key, self[key]]) }
self
end
def each_pair
return to_enum(:each_pair) unless block_given?
@keys.each { |key| yield(key, self[key]) }
self
end
alias_method :select, :find_all
def clear
super
@keys.clear
self
end
def shift
k = @keys.first
v = delete(k)
[k, v]
end
def merge!(other_hash)
if block_given?
other_hash.each { |k, v| self[k] = key?(k) ? yield(k, self[k], v) : v }
else
other_hash.each { |k, v| self[k] = v }
end
self
end
alias_method :update, :merge!
def merge(other_hash, &block)
dup.merge!(other_hash, &block)
end
# When replacing with another hash, the initial order of our keys must come from the other hash -ordered or not.
def replace(other)
super
@keys = other.keys
self
end
def inspect
"#<#{self.class} #{super}>"
end
private
def sync_keys!
@keys.delete_if { |k| !key?(k) }
end
end
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/core_ext/io_binary_read.rb 0000644 0000041 0000041 00000000464 13765541231 026575 0 ustar www-data www-data class IO #:nodoc:
class << self
unless method_defined? :binread
def binread(file, *args)
raise ArgumentError, "wrong number of arguments (#{1 + args.size} for 1..3)" unless args.size < 3
File.open(file, "rb") do |f|
f.read(*args)
end
end
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/parser/ 0000755 0000041 0000041 00000000000 13765541231 022762 5 ustar www-data www-data foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/parser/arguments.rb 0000644 0000041 0000041 00000010627 13765541231 025322 0 ustar www-data www-data class Foreman::Thor
class Arguments #:nodoc: # rubocop:disable ClassLength
NUMERIC = /[-+]?(\d*\.\d+|\d+)/
# Receives an array of args and returns two arrays, one with arguments
# and one with switches.
#
def self.split(args)
arguments = []
args.each do |item|
break if item =~ /^-/
arguments << item
end
[arguments, args[Range.new(arguments.size, -1)]]
end
def self.parse(*args)
to_parse = args.pop
new(*args).parse(to_parse)
end
# Takes an array of Foreman::Thor::Argument objects.
#
def initialize(arguments = [])
@assigns = {}
@non_assigned_required = []
@switches = arguments
arguments.each do |argument|
if !argument.default.nil?
@assigns[argument.human_name] = argument.default
elsif argument.required?
@non_assigned_required << argument
end
end
end
def parse(args)
@pile = args.dup
@switches.each do |argument|
break unless peek
@non_assigned_required.delete(argument)
@assigns[argument.human_name] = send(:"parse_#{argument.type}", argument.human_name)
end
check_requirement!
@assigns
end
def remaining
@pile
end
private
def no_or_skip?(arg)
arg =~ /^--(no|skip)-([-\w]+)$/
$2
end
def last?
@pile.empty?
end
def peek
@pile.first
end
def shift
@pile.shift
end
def unshift(arg)
if arg.is_a?(Array)
@pile = arg + @pile
else
@pile.unshift(arg)
end
end
def current_is_value?
peek && peek.to_s !~ /^-/
end
# Runs through the argument array getting strings that contains ":" and
# mark it as a hash:
#
# [ "name:string", "age:integer" ]
#
# Becomes:
#
# { "name" => "string", "age" => "integer" }
#
def parse_hash(name)
return shift if peek.is_a?(Hash)
hash = {}
while current_is_value? && peek.include?(":")
key, value = shift.split(":", 2)
raise MalformattedArgumentError, "You can't specify '#{key}' more than once in option '#{name}'; got #{key}:#{hash[key]} and #{key}:#{value}" if hash.include? key
hash[key] = value
end
hash
end
# Runs through the argument array getting all strings until no string is
# found or a switch is found.
#
# ["a", "b", "c"]
#
# And returns it as an array:
#
# ["a", "b", "c"]
#
def parse_array(name)
return shift if peek.is_a?(Array)
array = []
array << shift while current_is_value?
array
end
# Check if the peek is numeric format and return a Float or Integer.
# Check if the peek is included in enum if enum is provided.
# Otherwise raises an error.
#
def parse_numeric(name)
return shift if peek.is_a?(Numeric)
unless peek =~ NUMERIC && $& == peek
raise MalformattedArgumentError, "Expected numeric value for '#{name}'; got #{peek.inspect}"
end
value = $&.index(".") ? shift.to_f : shift.to_i
if @switches.is_a?(Hash) && switch = @switches[name]
if switch.enum && !switch.enum.include?(value)
raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
end
end
value
end
# Parse string:
# for --string-arg, just return the current value in the pile
# for --no-string-arg, nil
# Check if the peek is included in enum if enum is provided. Otherwise raises an error.
#
def parse_string(name)
if no_or_skip?(name)
nil
else
value = shift
if @switches.is_a?(Hash) && switch = @switches[name]
if switch.enum && !switch.enum.include?(value)
raise MalformattedArgumentError, "Expected '#{name}' to be one of #{switch.enum.join(', ')}; got #{value}"
end
end
value
end
end
# Raises an error if @non_assigned_required array is not empty.
#
def check_requirement!
return if @non_assigned_required.empty?
names = @non_assigned_required.map do |o|
o.respond_to?(:switch_name) ? o.switch_name : o.human_name
end.join("', '")
class_name = self.class.name.split("::").last.downcase
raise RequiredArgumentMissingError, "No value provided for required #{class_name} '#{names}'"
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/parser/option.rb 0000644 0000041 0000041 00000007612 13765541231 024625 0 ustar www-data www-data class Foreman::Thor
class Option < Argument #:nodoc:
attr_reader :aliases, :group, :lazy_default, :hide
VALID_TYPES = [:boolean, :numeric, :hash, :array, :string]
def initialize(name, options = {})
options[:required] = false unless options.key?(:required)
super
@lazy_default = options[:lazy_default]
@group = options[:group].to_s.capitalize if options[:group]
@aliases = Array(options[:aliases])
@hide = options[:hide]
end
# This parse quick options given as method_options. It makes several
# assumptions, but you can be more specific using the option method.
#
# parse :foo => "bar"
# #=> Option foo with default value bar
#
# parse [:foo, :baz] => "bar"
# #=> Option foo with default value bar and alias :baz
#
# parse :foo => :required
# #=> Required option foo without default value
#
# parse :foo => 2
# #=> Option foo with default value 2 and type numeric
#
# parse :foo => :numeric
# #=> Option foo without default value and type numeric
#
# parse :foo => true
# #=> Option foo with default value true and type boolean
#
# The valid types are :boolean, :numeric, :hash, :array and :string. If none
# is given a default type is assumed. This default type accepts arguments as
# string (--foo=value) or booleans (just --foo).
#
# By default all options are optional, unless :required is given.
#
def self.parse(key, value)
if key.is_a?(Array)
name, *aliases = key
else
name = key
aliases = []
end
name = name.to_s
default = value
type = case value
when Symbol
default = nil
if VALID_TYPES.include?(value)
value
elsif required = (value == :required) # rubocop:disable AssignmentInCondition
:string
end
when TrueClass, FalseClass
:boolean
when Numeric
:numeric
when Hash, Array, String
value.class.name.downcase.to_sym
end
new(name.to_s, :required => required, :type => type, :default => default, :aliases => aliases)
end
def switch_name
@switch_name ||= dasherized? ? name : dasherize(name)
end
def human_name
@human_name ||= dasherized? ? undasherize(name) : name
end
def usage(padding = 0)
sample = if banner && !banner.to_s.empty?
"#{switch_name}=#{banner}"
else
switch_name
end
sample = "[#{sample}]" unless required?
if boolean?
sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.start_with?("no-")
end
if aliases.empty?
(" " * padding) << sample
else
"#{aliases.join(', ')}, #{sample}"
end
end
VALID_TYPES.each do |type|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{type}?
self.type == #{type.inspect}
end
RUBY
end
protected
def validate!
raise ArgumentError, "An option cannot be boolean and required." if boolean? && required?
validate_default_type!
end
def validate_default_type!
default_type = case @default
when nil
return
when TrueClass, FalseClass
required? ? :string : :boolean
when Numeric
:numeric
when Symbol
:string
when Hash, Array, String
@default.class.name.downcase.to_sym
end
# TODO: This should raise an ArgumentError in a future version of Foreman::Thor
warn "Expected #{@type} default value for '#{switch_name}'; got #{@default.inspect} (#{default_type})" unless default_type == @type
end
def dasherized?
name.index("-") == 0
end
def undasherize(str)
str.sub(/^-{1,2}/, "")
end
def dasherize(str)
(str.length > 1 ? "--" : "-") + str.tr("_", "-")
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/parser/options.rb 0000644 0000041 0000041 00000013465 13765541231 025013 0 ustar www-data www-data class Foreman::Thor
class Options < Arguments #:nodoc: # rubocop:disable ClassLength
LONG_RE = /^(--\w+(?:-\w+)*)$/
SHORT_RE = /^(-[a-z])$/i
EQ_RE = /^(--\w+(?:-\w+)*|-[a-z])=(.*)$/i
SHORT_SQ_RE = /^-([a-z]{2,})$/i # Allow either -x -v or -xv style for single char args
SHORT_NUM = /^(-[a-z])#{NUMERIC}$/i
OPTS_END = "--".freeze
# Receives a hash and makes it switches.
def self.to_switches(options)
options.map do |key, value|
case value
when true
"--#{key}"
when Array
"--#{key} #{value.map(&:inspect).join(' ')}"
when Hash
"--#{key} #{value.map { |k, v| "#{k}:#{v}" }.join(' ')}"
when nil, false
""
else
"--#{key} #{value.inspect}"
end
end.join(" ")
end
# Takes a hash of Foreman::Thor::Option and a hash with defaults.
#
# If +stop_on_unknown+ is true, #parse will stop as soon as it encounters
# an unknown option or a regular argument.
def initialize(hash_options = {}, defaults = {}, stop_on_unknown = false)
@stop_on_unknown = stop_on_unknown
options = hash_options.values
super(options)
# Add defaults
defaults.each do |key, value|
@assigns[key.to_s] = value
@non_assigned_required.delete(hash_options[key])
end
@shorts = {}
@switches = {}
@extra = []
options.each do |option|
@switches[option.switch_name] = option
option.aliases.each do |short|
name = short.to_s.sub(/^(?!\-)/, "-")
@shorts[name] ||= option.switch_name
end
end
end
def remaining
@extra
end
def peek
return super unless @parsing_options
result = super
if result == OPTS_END
shift
@parsing_options = false
super
else
result
end
end
def parse(args) # rubocop:disable MethodLength
@pile = args.dup
@parsing_options = true
while peek
if parsing_options?
match, is_switch = current_is_switch?
shifted = shift
if is_switch
case shifted
when SHORT_SQ_RE
unshift($1.split("").map { |f| "-#{f}" })
next
when EQ_RE, SHORT_NUM
unshift($2)
switch = $1
when LONG_RE, SHORT_RE
switch = $1
end
switch = normalize_switch(switch)
option = switch_option(switch)
@assigns[option.human_name] = parse_peek(switch, option)
elsif @stop_on_unknown
@parsing_options = false
@extra << shifted
@extra << shift while peek
break
elsif match
@extra << shifted
@extra << shift while peek && peek !~ /^-/
else
@extra << shifted
end
else
@extra << shift
end
end
check_requirement!
assigns = Foreman::Thor::CoreExt::HashWithIndifferentAccess.new(@assigns)
assigns.freeze
assigns
end
def check_unknown!
# an unknown option starts with - or -- and has no more --'s afterward.
unknown = @extra.select { |str| str =~ /^--?(?:(?!--).)*$/ }
raise UnknownArgumentError, "Unknown switches '#{unknown.join(', ')}'" unless unknown.empty?
end
protected
# Check if the current value in peek is a registered switch.
#
# Two booleans are returned. The first is true if the current value
# starts with a hyphen; the second is true if it is a registered switch.
def current_is_switch?
case peek
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM
[true, switch?($1)]
when SHORT_SQ_RE
[true, $1.split("").any? { |f| switch?("-#{f}") }]
else
[false, false]
end
end
def current_is_switch_formatted?
case peek
when LONG_RE, SHORT_RE, EQ_RE, SHORT_NUM, SHORT_SQ_RE
true
else
false
end
end
def current_is_value?
peek && (!parsing_options? || super)
end
def switch?(arg)
switch_option(normalize_switch(arg))
end
def switch_option(arg)
if match = no_or_skip?(arg) # rubocop:disable AssignmentInCondition
@switches[arg] || @switches["--#{match}"]
else
@switches[arg]
end
end
# Check if the given argument is actually a shortcut.
#
def normalize_switch(arg)
(@shorts[arg] || arg).tr("_", "-")
end
def parsing_options?
peek
@parsing_options
end
# Parse boolean values which can be given as --foo=true, --foo or --no-foo.
#
def parse_boolean(switch)
if current_is_value?
if ["true", "TRUE", "t", "T", true].include?(peek)
shift
true
elsif ["false", "FALSE", "f", "F", false].include?(peek)
shift
false
else
true
end
else
@switches.key?(switch) || !no_or_skip?(switch)
end
end
# Parse the value at the peek analyzing if it requires an input or not.
#
def parse_peek(switch, option)
if parsing_options? && (current_is_switch_formatted? || last?)
if option.boolean?
# No problem for boolean types
elsif no_or_skip?(switch)
return nil # User set value to nil
elsif option.string? && !option.required?
# Return the default if there is one, else the human name
return option.lazy_default || option.default || option.human_name
elsif option.lazy_default
return option.lazy_default
else
raise MalformattedArgumentError, "No value provided for option '#{switch}'"
end
end
@non_assigned_required.delete(option)
send(:"parse_#{option.type}", switch)
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/parser/argument.rb 0000644 0000041 0000041 00000003404 13765541231 025132 0 ustar www-data www-data class Foreman::Thor
class Argument #:nodoc:
VALID_TYPES = [:numeric, :hash, :array, :string]
attr_reader :name, :description, :enum, :required, :type, :default, :banner
alias_method :human_name, :name
def initialize(name, options = {})
class_name = self.class.name.split("::").last
type = options[:type]
raise ArgumentError, "#{class_name} name can't be nil." if name.nil?
raise ArgumentError, "Type :#{type} is not valid for #{class_name.downcase}s." if type && !valid_type?(type)
@name = name.to_s
@description = options[:desc]
@required = options.key?(:required) ? options[:required] : true
@type = (type || :string).to_sym
@default = options[:default]
@banner = options[:banner] || default_banner
@enum = options[:enum]
validate! # Trigger specific validations
end
def usage
required? ? banner : "[#{banner}]"
end
def required?
required
end
def show_default?
case default
when Array, String, Hash
!default.empty?
else
default
end
end
protected
def validate!
raise ArgumentError, "An argument cannot be required and have default value." if required? && !default.nil?
raise ArgumentError, "An argument cannot have an enum other than an array." if @enum && !@enum.is_a?(Array)
end
def valid_type?(type)
self.class::VALID_TYPES.include?(type.to_sym)
end
def default_banner
case type
when :boolean
nil
when :string, :default
human_name.upcase
when :numeric
"N"
when :hash
"key:value"
when :array
"one two three"
end
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/runner.rb 0000644 0000041 0000041 00000023675 13765541231 023341 0 ustar www-data www-data require "foreman/vendor/thor/lib/thor"
require "foreman/vendor/thor/lib/thor/group"
require "foreman/vendor/thor/lib/thor/core_ext/io_binary_read"
require "fileutils"
require "open-uri"
require "yaml"
require "digest/md5"
require "pathname"
class Foreman::Thor::Runner < Foreman::Thor #:nodoc: # rubocop:disable ClassLength
map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version
def self.banner(command, all = false, subcommand = false)
"thor " + command.formatted_usage(self, all, subcommand)
end
def self.exit_on_failure?
true
end
# Override Foreman::Thor#help so it can give information about any class and any method.
#
def help(meth = nil)
if meth && !respond_to?(meth)
initialize_thorfiles(meth)
klass, command = Foreman::Thor::Util.find_class_and_command_by_namespace(meth)
self.class.handle_no_command_error(command, false) if klass.nil?
klass.start(["-h", command].compact, :shell => shell)
else
super
end
end
# If a command is not found on Foreman::Thor::Runner, method missing is invoked and
# Foreman::Thor::Runner is then responsible for finding the command in all classes.
#
def method_missing(meth, *args)
meth = meth.to_s
initialize_thorfiles(meth)
klass, command = Foreman::Thor::Util.find_class_and_command_by_namespace(meth)
self.class.handle_no_command_error(command, false) if klass.nil?
args.unshift(command) if command
klass.start(args, :shell => shell)
end
desc "install NAME", "Install an optionally named Foreman::Thor file into your system commands"
method_options :as => :string, :relative => :boolean, :force => :boolean
def install(name) # rubocop:disable MethodLength
initialize_thorfiles
# If a directory name is provided as the argument, look for a 'main.thor'
# command in said directory.
begin
if File.directory?(File.expand_path(name))
base = File.join(name, "main.thor")
package = :directory
contents = open(base, &:read)
else
base = name
package = :file
contents = open(name, &:read)
end
rescue OpenURI::HTTPError
raise Error, "Error opening URI '#{name}'"
rescue Errno::ENOENT
raise Error, "Error opening file '#{name}'"
end
say "Your Foreman::Thorfile contains:"
say contents
unless options["force"]
return false if no?("Do you wish to continue [y/N]?")
end
as = options["as"] || begin
first_line = contents.split("\n")[0]
(match = first_line.match(/\s*#\s*module:\s*([^\n]*)/)) ? match[1].strip : nil
end
unless as
basename = File.basename(name)
as = ask("Please specify a name for #{name} in the system repository [#{basename}]:")
as = basename if as.empty?
end
location = if options[:relative] || name =~ %r{^https?://}
name
else
File.expand_path(name)
end
thor_yaml[as] = {
:filename => Digest::MD5.hexdigest(name + as),
:location => location,
:namespaces => Foreman::Thor::Util.namespaces_in_content(contents, base)
}
save_yaml(thor_yaml)
say "Storing thor file in your system repository"
destination = File.join(thor_root, thor_yaml[as][:filename])
if package == :file
File.open(destination, "w") { |f| f.puts contents }
else
FileUtils.cp_r(name, destination)
end
thor_yaml[as][:filename] # Indicate success
end
desc "version", "Show Foreman::Thor version"
def version
require "foreman/vendor/thor/lib/thor/version"
say "Foreman::Thor #{Foreman::Thor::VERSION}"
end
desc "uninstall NAME", "Uninstall a named Foreman::Thor module"
def uninstall(name)
raise Error, "Can't find module '#{name}'" unless thor_yaml[name]
say "Uninstalling #{name}."
FileUtils.rm_rf(File.join(thor_root, (thor_yaml[name][:filename]).to_s))
thor_yaml.delete(name)
save_yaml(thor_yaml)
puts "Done."
end
desc "update NAME", "Update a Foreman::Thor file from its original location"
def update(name)
raise Error, "Can't find module '#{name}'" if !thor_yaml[name] || !thor_yaml[name][:location]
say "Updating '#{name}' from #{thor_yaml[name][:location]}"
old_filename = thor_yaml[name][:filename]
self.options = options.merge("as" => name)
if File.directory? File.expand_path(name)
FileUtils.rm_rf(File.join(thor_root, old_filename))
thor_yaml.delete(old_filename)
save_yaml(thor_yaml)
filename = install(name)
else
filename = install(thor_yaml[name][:location])
end
File.delete(File.join(thor_root, old_filename)) unless filename == old_filename
end
desc "installed", "List the installed Foreman::Thor modules and commands"
method_options :internal => :boolean
def installed
initialize_thorfiles(nil, true)
display_klasses(true, options["internal"])
end
desc "list [SEARCH]", "List the available thor commands (--substring means .*SEARCH)"
method_options :substring => :boolean, :group => :string, :all => :boolean, :debug => :boolean
def list(search = "")
initialize_thorfiles
search = ".*#{search}" if options["substring"]
search = /^#{search}.*/i
group = options[:group] || "standard"
klasses = Foreman::Thor::Base.subclasses.select do |k|
(options[:all] || k.group == group) && k.namespace =~ search
end
display_klasses(false, false, klasses)
end
private
def thor_root
Foreman::Thor::Util.thor_root
end
def thor_yaml
@thor_yaml ||= begin
yaml_file = File.join(thor_root, "thor.yml")
yaml = YAML.load_file(yaml_file) if File.exist?(yaml_file)
yaml || {}
end
end
# Save the yaml file. If none exists in thor root, creates one.
#
def save_yaml(yaml)
yaml_file = File.join(thor_root, "thor.yml")
unless File.exist?(yaml_file)
FileUtils.mkdir_p(thor_root)
yaml_file = File.join(thor_root, "thor.yml")
FileUtils.touch(yaml_file)
end
File.open(yaml_file, "w") { |f| f.puts yaml.to_yaml }
end
# Load the Foreman::Thorfiles. If relevant_to is supplied, looks for specific files
# in the thor_root instead of loading them all.
#
# By default, it also traverses the current path until find Foreman::Thor files, as
# described in thorfiles. This look up can be skipped by supplying
# skip_lookup true.
#
def initialize_thorfiles(relevant_to = nil, skip_lookup = false)
thorfiles(relevant_to, skip_lookup).each do |f|
Foreman::Thor::Util.load_thorfile(f, nil, options[:debug]) unless Foreman::Thor::Base.subclass_files.keys.include?(File.expand_path(f))
end
end
# Finds Foreman::Thorfiles by traversing from your current directory down to the root
# directory of your system. If at any time we find a Foreman::Thor file, we stop.
#
# We also ensure that system-wide Foreman::Thorfiles are loaded first, so local
# Foreman::Thorfiles can override them.
#
# ==== Example
#
# If we start at /Users/wycats/dev/thor ...
#
# 1. /Users/wycats/dev/thor
# 2. /Users/wycats/dev
# 3. /Users/wycats <-- we find a Foreman::Thorfile here, so we stop
#
# Suppose we start at c:\Documents and Settings\james\dev\thor ...
#
# 1. c:\Documents and Settings\james\dev\thor
# 2. c:\Documents and Settings\james\dev
# 3. c:\Documents and Settings\james
# 4. c:\Documents and Settings
# 5. c:\ <-- no Foreman::Thorfiles found!
#
def thorfiles(relevant_to = nil, skip_lookup = false)
thorfiles = []
unless skip_lookup
Pathname.pwd.ascend do |path|
thorfiles = Foreman::Thor::Util.globs_for(path).map { |g| Dir[g] }.flatten
break unless thorfiles.empty?
end
end
files = (relevant_to ? thorfiles_relevant_to(relevant_to) : Foreman::Thor::Util.thor_root_glob)
files += thorfiles
files -= ["#{thor_root}/thor.yml"]
files.map! do |file|
File.directory?(file) ? File.join(file, "main.thor") : file
end
end
# Load Foreman::Thorfiles relevant to the given method. If you provide "foo:bar" it
# will load all thor files in the thor.yaml that has "foo" e "foo:bar"
# namespaces registered.
#
def thorfiles_relevant_to(meth)
lookup = [meth, meth.split(":")[0...-1].join(":")]
files = thor_yaml.select do |_, v|
v[:namespaces] && !(v[:namespaces] & lookup).empty?
end
files.map { |_, v| File.join(thor_root, (v[:filename]).to_s) }
end
# Display information about the given klasses. If with_module is given,
# it shows a table with information extracted from the yaml file.
#
def display_klasses(with_modules = false, show_internal = false, klasses = Foreman::Thor::Base.subclasses)
klasses -= [Foreman::Thor, Foreman::Thor::Runner, Foreman::Thor::Group] unless show_internal
raise Error, "No Foreman::Thor commands available" if klasses.empty?
show_modules if with_modules && !thor_yaml.empty?
list = Hash.new { |h, k| h[k] = [] }
groups = klasses.select { |k| k.ancestors.include?(Foreman::Thor::Group) }
# Get classes which inherit from Foreman::Thor
(klasses - groups).each { |k| list[k.namespace.split(":").first] += k.printable_commands(false) }
# Get classes which inherit from Foreman::Thor::Base
groups.map! { |k| k.printable_commands(false).first }
list["root"] = groups
# Order namespaces with default coming first
list = list.sort { |a, b| a[0].sub(/^default/, "") <=> b[0].sub(/^default/, "") }
list.each { |n, commands| display_commands(n, commands) unless commands.empty? }
end
def display_commands(namespace, list) #:nodoc:
list.sort! { |a, b| a[0] <=> b[0] }
say shell.set_color(namespace, :blue, true)
say "-" * namespace.size
print_table(list, :truncate => true)
say
end
alias_method :display_tasks, :display_commands
def show_modules #:nodoc:
info = []
labels = %w(Modules Namespaces)
info << labels
info << ["-" * labels[0].size, "-" * labels[1].size]
thor_yaml.each do |name, hash|
info << [name, hash[:namespaces].join(", ")]
end
print_table info
say ""
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/line_editor.rb 0000644 0000041 0000041 00000000657 13765541231 024320 0 ustar www-data www-data require "foreman/vendor/thor/lib/thor/line_editor/basic"
require "foreman/vendor/thor/lib/thor/line_editor/readline"
class Foreman::Thor
module LineEditor
def self.readline(prompt, options = {})
best_available.new(prompt, options).readline
end
def self.best_available
[
Foreman::Thor::LineEditor::Readline,
Foreman::Thor::LineEditor::Basic
].detect(&:available?)
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/version.rb 0000644 0000041 0000041 00000000055 13765541231 023500 0 ustar www-data www-data class Foreman::Thor
VERSION = "0.19.4"
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/group.rb 0000644 0000041 0000041 00000021561 13765541231 023154 0 ustar www-data www-data require "foreman/vendor/thor/lib/thor/base"
# Foreman::Thor has a special class called Foreman::Thor::Group. The main difference to Foreman::Thor class
# is that it invokes all commands at once. It also include some methods that allows
# invocations to be done at the class method, which are not available to Foreman::Thor
# commands.
class Foreman::Thor::Group
class << self
# The description for this Foreman::Thor::Group. If none is provided, but a source root
# exists, tries to find the USAGE one folder above it, otherwise searches
# in the superclass.
#
# ==== Parameters
# description:: The description for this Foreman::Thor::Group.
#
def desc(description = nil)
if description
@desc = description
else
@desc ||= from_superclass(:desc, nil)
end
end
# Prints help information.
#
# ==== Options
# short:: When true, shows only usage.
#
def help(shell)
shell.say "Usage:"
shell.say " #{banner}\n"
shell.say
class_options_help(shell)
shell.say desc if desc
end
# Stores invocations for this class merging with superclass values.
#
def invocations #:nodoc:
@invocations ||= from_superclass(:invocations, {})
end
# Stores invocation blocks used on invoke_from_option.
#
def invocation_blocks #:nodoc:
@invocation_blocks ||= from_superclass(:invocation_blocks, {})
end
# Invoke the given namespace or class given. It adds an instance
# method that will invoke the klass and command. You can give a block to
# configure how it will be invoked.
#
# The namespace/class given will have its options showed on the help
# usage. Check invoke_from_option for more information.
#
def invoke(*names, &block)
options = names.last.is_a?(Hash) ? names.pop : {}
verbose = options.fetch(:verbose, true)
names.each do |name|
invocations[name] = false
invocation_blocks[name] = block if block_given?
class_eval <<-METHOD, __FILE__, __LINE__
def _invoke_#{name.to_s.gsub(/\W/, '_')}
klass, command = self.class.prepare_for_invocation(nil, #{name.inspect})
if klass
say_status :invoke, #{name.inspect}, #{verbose.inspect}
block = self.class.invocation_blocks[#{name.inspect}]
_invoke_for_class_method klass, command, &block
else
say_status :error, %(#{name.inspect} [not found]), :red
end
end
METHOD
end
end
# Invoke a thor class based on the value supplied by the user to the
# given option named "name". A class option must be created before this
# method is invoked for each name given.
#
# ==== Examples
#
# class GemGenerator < Foreman::Thor::Group
# class_option :test_framework, :type => :string
# invoke_from_option :test_framework
# end
#
# ==== Boolean options
#
# In some cases, you want to invoke a thor class if some option is true or
# false. This is automatically handled by invoke_from_option. Then the
# option name is used to invoke the generator.
#
# ==== Preparing for invocation
#
# In some cases you want to customize how a specified hook is going to be
# invoked. You can do that by overwriting the class method
# prepare_for_invocation. The class method must necessarily return a klass
# and an optional command.
#
# ==== Custom invocations
#
# You can also supply a block to customize how the option is going to be
# invoked. The block receives two parameters, an instance of the current
# class and the klass to be invoked.
#
def invoke_from_option(*names, &block)
options = names.last.is_a?(Hash) ? names.pop : {}
verbose = options.fetch(:verbose, :white)
names.each do |name|
unless class_options.key?(name)
raise ArgumentError, "You have to define the option #{name.inspect} " \
"before setting invoke_from_option."
end
invocations[name] = true
invocation_blocks[name] = block if block_given?
class_eval <<-METHOD, __FILE__, __LINE__
def _invoke_from_option_#{name.to_s.gsub(/\W/, '_')}
return unless options[#{name.inspect}]
value = options[#{name.inspect}]
value = #{name.inspect} if TrueClass === value
klass, command = self.class.prepare_for_invocation(#{name.inspect}, value)
if klass
say_status :invoke, value, #{verbose.inspect}
block = self.class.invocation_blocks[#{name.inspect}]
_invoke_for_class_method klass, command, &block
else
say_status :error, %(\#{value} [not found]), :red
end
end
METHOD
end
end
# Remove a previously added invocation.
#
# ==== Examples
#
# remove_invocation :test_framework
#
def remove_invocation(*names)
names.each do |name|
remove_command(name)
remove_class_option(name)
invocations.delete(name)
invocation_blocks.delete(name)
end
end
# Overwrite class options help to allow invoked generators options to be
# shown recursively when invoking a generator.
#
def class_options_help(shell, groups = {}) #:nodoc:
get_options_from_invocations(groups, class_options) do |klass|
klass.send(:get_options_from_invocations, groups, class_options)
end
super(shell, groups)
end
# Get invocations array and merge options from invocations. Those
# options are added to group_options hash. Options that already exists
# in base_options are not added twice.
#
def get_options_from_invocations(group_options, base_options) #:nodoc: # rubocop:disable MethodLength
invocations.each do |name, from_option|
value = if from_option
option = class_options[name]
option.type == :boolean ? name : option.default
else
name
end
next unless value
klass, _ = prepare_for_invocation(name, value)
next unless klass && klass.respond_to?(:class_options)
value = value.to_s
human_name = value.respond_to?(:classify) ? value.classify : value
group_options[human_name] ||= []
group_options[human_name] += klass.class_options.values.select do |class_option|
base_options[class_option.name.to_sym].nil? && class_option.group.nil? &&
!group_options.values.flatten.any? { |i| i.name == class_option.name }
end
yield klass if block_given?
end
end
# Returns commands ready to be printed.
def printable_commands(*)
item = []
item << banner
item << (desc ? "# #{desc.gsub(/\s+/m, ' ')}" : "")
[item]
end
alias_method :printable_tasks, :printable_commands
def handle_argument_error(command, error, _args, arity) #:nodoc:
msg = "#{basename} #{command.name} takes #{arity} argument"
msg << "s" if arity > 1
msg << ", but it should not."
raise error, msg
end
protected
# The method responsible for dispatching given the args.
def dispatch(command, given_args, given_opts, config) #:nodoc:
if Foreman::Thor::HELP_MAPPINGS.include?(given_args.first)
help(config[:shell])
return
end
args, opts = Foreman::Thor::Options.split(given_args)
opts = given_opts || opts
instance = new(args, opts, config)
yield instance if block_given?
if command
instance.invoke_command(all_commands[command])
else
instance.invoke_all
end
end
# The banner for this class. You can customize it if you are invoking the
# thor class by another ways which is not the Foreman::Thor::Runner.
def banner
"#{basename} #{self_command.formatted_usage(self, false)}"
end
# Represents the whole class as a command.
def self_command #:nodoc:
Foreman::Thor::DynamicCommand.new(namespace, class_options)
end
alias_method :self_task, :self_command
def baseclass #:nodoc:
Foreman::Thor::Group
end
def create_command(meth) #:nodoc:
commands[meth.to_s] = Foreman::Thor::Command.new(meth, nil, nil, nil, nil)
true
end
alias_method :create_task, :create_command
end
include Foreman::Thor::Base
protected
# Shortcut to invoke with padding and block handling. Use internally by
# invoke and invoke_from_option class methods.
def _invoke_for_class_method(klass, command = nil, *args, &block) #:nodoc:
with_padding do
if block
case block.arity
when 3
yield(self, klass, command)
when 2
yield(self, klass)
when 1
instance_exec(klass, &block)
end
else
invoke klass, command, *args
end
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/actions.rb 0000644 0000041 0000041 00000024067 13765541231 023464 0 ustar www-data www-data require "fileutils"
require "uri"
require "foreman/vendor/thor/lib/thor/core_ext/io_binary_read"
require "foreman/vendor/thor/lib/thor/actions/create_file"
require "foreman/vendor/thor/lib/thor/actions/create_link"
require "foreman/vendor/thor/lib/thor/actions/directory"
require "foreman/vendor/thor/lib/thor/actions/empty_directory"
require "foreman/vendor/thor/lib/thor/actions/file_manipulation"
require "foreman/vendor/thor/lib/thor/actions/inject_into_file"
class Foreman::Thor
module Actions
attr_accessor :behavior
def self.included(base) #:nodoc:
base.extend ClassMethods
end
module ClassMethods
# Hold source paths for one Foreman::Thor instance. source_paths_for_search is the
# method responsible to gather source_paths from this current class,
# inherited paths and the source root.
#
def source_paths
@_source_paths ||= []
end
# Stores and return the source root for this class
def source_root(path = nil)
@_source_root = path if path
@_source_root ||= nil
end
# Returns the source paths in the following order:
#
# 1) This class source paths
# 2) Source root
# 3) Parents source paths
#
def source_paths_for_search
paths = []
paths += source_paths
paths << source_root if source_root
paths += from_superclass(:source_paths, [])
paths
end
# Add runtime options that help actions execution.
#
def add_runtime_options!
class_option :force, :type => :boolean, :aliases => "-f", :group => :runtime,
:desc => "Overwrite files that already exist"
class_option :pretend, :type => :boolean, :aliases => "-p", :group => :runtime,
:desc => "Run but do not make any changes"
class_option :quiet, :type => :boolean, :aliases => "-q", :group => :runtime,
:desc => "Suppress status output"
class_option :skip, :type => :boolean, :aliases => "-s", :group => :runtime,
:desc => "Skip files that already exist"
end
end
# Extends initializer to add more configuration options.
#
# ==== Configuration
# behavior:: The actions default behavior. Can be :invoke or :revoke.
# It also accepts :force, :skip and :pretend to set the behavior
# and the respective option.
#
# destination_root:: The root directory needed for some actions.
#
def initialize(args = [], options = {}, config = {})
self.behavior = case config[:behavior].to_s
when "force", "skip"
_cleanup_options_and_set(options, config[:behavior])
:invoke
when "revoke"
:revoke
else
:invoke
end
super
self.destination_root = config[:destination_root]
end
# Wraps an action object and call it accordingly to the thor class behavior.
#
def action(instance) #:nodoc:
if behavior == :revoke
instance.revoke!
else
instance.invoke!
end
end
# Returns the root for this thor class (also aliased as destination root).
#
def destination_root
@destination_stack.last
end
# Sets the root for this thor class. Relatives path are added to the
# directory where the script was invoked and expanded.
#
def destination_root=(root)
@destination_stack ||= []
@destination_stack[0] = File.expand_path(root || "")
end
# Returns the given path relative to the absolute root (ie, root where
# the script started).
#
def relative_to_original_destination_root(path, remove_dot = true)
path = path.dup
if path.gsub!(@destination_stack[0], ".")
remove_dot ? (path[2..-1] || "") : path
else
path
end
end
# Holds source paths in instance so they can be manipulated.
#
def source_paths
@source_paths ||= self.class.source_paths_for_search
end
# Receives a file or directory and search for it in the source paths.
#
def find_in_source_paths(file)
possible_files = [file, file + TEMPLATE_EXTNAME]
relative_root = relative_to_original_destination_root(destination_root, false)
source_paths.each do |source|
possible_files.each do |f|
source_file = File.expand_path(f, File.join(source, relative_root))
return source_file if File.exist?(source_file)
end
end
message = "Could not find #{file.inspect} in any of your source paths. "
unless self.class.source_root
message << "Please invoke #{self.class.name}.source_root(PATH) with the PATH containing your templates. "
end
message << if source_paths.empty?
"Currently you have no source paths."
else
"Your current source paths are: \n#{source_paths.join("\n")}"
end
raise Error, message
end
# Do something in the root or on a provided subfolder. If a relative path
# is given it's referenced from the current root. The full path is yielded
# to the block you provide. The path is set back to the previous path when
# the method exits.
#
# ==== Parameters
# dir:: the directory to move to.
# config:: give :verbose => true to log and use padding.
#
def inside(dir = "", config = {}, &block)
verbose = config.fetch(:verbose, false)
pretend = options[:pretend]
say_status :inside, dir, verbose
shell.padding += 1 if verbose
@destination_stack.push File.expand_path(dir, destination_root)
# If the directory doesnt exist and we're not pretending
if !File.exist?(destination_root) && !pretend
FileUtils.mkdir_p(destination_root)
end
if pretend
# In pretend mode, just yield down to the block
block.arity == 1 ? yield(destination_root) : yield
else
FileUtils.cd(destination_root) { block.arity == 1 ? yield(destination_root) : yield }
end
@destination_stack.pop
shell.padding -= 1 if verbose
end
# Goes to the root and execute the given block.
#
def in_root
inside(@destination_stack.first) { yield }
end
# Loads an external file and execute it in the instance binding.
#
# ==== Parameters
# path:: The path to the file to execute. Can be a web address or
# a relative path from the source root.
#
# ==== Examples
#
# apply "http://gist.github.com/103208"
#
# apply "recipes/jquery.rb"
#
def apply(path, config = {})
verbose = config.fetch(:verbose, true)
is_uri = path =~ %r{^https?\://}
path = find_in_source_paths(path) unless is_uri
say_status :apply, path, verbose
shell.padding += 1 if verbose
contents = if is_uri
open(path, "Accept" => "application/x-thor-template", &:read)
else
open(path, &:read)
end
instance_eval(contents, path)
shell.padding -= 1 if verbose
end
# Executes a command returning the contents of the command.
#
# ==== Parameters
# command:: the command to be executed.
# config:: give :verbose => false to not log the status, :capture => true to hide to output. Specify :with
# to append an executable to command execution.
#
# ==== Example
#
# inside('vendor') do
# run('ln -s ~/edge rails')
# end
#
def run(command, config = {})
return unless behavior == :invoke
destination = relative_to_original_destination_root(destination_root, false)
desc = "#{command} from #{destination.inspect}"
if config[:with]
desc = "#{File.basename(config[:with].to_s)} #{desc}"
command = "#{config[:with]} #{command}"
end
say_status :run, desc, config.fetch(:verbose, true)
!options[:pretend] && config[:capture] ? `#{command}` : system(command.to_s)
end
# Executes a ruby script (taking into account WIN32 platform quirks).
#
# ==== Parameters
# command:: the command to be executed.
# config:: give :verbose => false to not log the status.
#
def run_ruby_script(command, config = {})
return unless behavior == :invoke
run command, config.merge(:with => Foreman::Thor::Util.ruby_command)
end
# Run a thor command. A hash of options can be given and it's converted to
# switches.
#
# ==== Parameters
# command:: the command to be invoked
# args:: arguments to the command
# config:: give :verbose => false to not log the status, :capture => true to hide to output.
# Other options are given as parameter to Foreman::Thor.
#
#
# ==== Examples
#
# thor :install, "http://gist.github.com/103208"
# #=> thor install http://gist.github.com/103208
#
# thor :list, :all => true, :substring => 'rails'
# #=> thor list --all --substring=rails
#
def thor(command, *args)
config = args.last.is_a?(Hash) ? args.pop : {}
verbose = config.key?(:verbose) ? config.delete(:verbose) : true
pretend = config.key?(:pretend) ? config.delete(:pretend) : false
capture = config.key?(:capture) ? config.delete(:capture) : false
args.unshift(command)
args.push Foreman::Thor::Options.to_switches(config)
command = args.join(" ").strip
run command, :with => :thor, :verbose => verbose, :pretend => pretend, :capture => capture
end
protected
# Allow current root to be shared between invocations.
#
def _shared_configuration #:nodoc:
super.merge!(:destination_root => destination_root)
end
def _cleanup_options_and_set(options, key) #:nodoc:
case options
when Array
%w(--force -f --skip -s).each { |i| options.delete(i) }
options << "--#{key}"
when Hash
[:force, :skip, "force", "skip"].each { |i| options.delete(i) }
options.merge!(key => true)
end
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/shell/ 0000755 0000041 0000041 00000000000 13765541231 022575 5 ustar www-data www-data foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/shell/color.rb 0000644 0000041 0000041 00000011257 13765541231 024246 0 ustar www-data www-data require "foreman/vendor/thor/lib/thor/shell/basic"
class Foreman::Thor
module Shell
# Inherit from Foreman::Thor::Shell::Basic and add set_color behavior. Check
# Foreman::Thor::Shell::Basic to see all available methods.
#
class Color < Basic
# Embed in a String to clear all previous ANSI sequences.
CLEAR = "\e[0m"
# The start of an ANSI bold sequence.
BOLD = "\e[1m"
# Set the terminal's foreground ANSI color to black.
BLACK = "\e[30m"
# Set the terminal's foreground ANSI color to red.
RED = "\e[31m"
# Set the terminal's foreground ANSI color to green.
GREEN = "\e[32m"
# Set the terminal's foreground ANSI color to yellow.
YELLOW = "\e[33m"
# Set the terminal's foreground ANSI color to blue.
BLUE = "\e[34m"
# Set the terminal's foreground ANSI color to magenta.
MAGENTA = "\e[35m"
# Set the terminal's foreground ANSI color to cyan.
CYAN = "\e[36m"
# Set the terminal's foreground ANSI color to white.
WHITE = "\e[37m"
# Set the terminal's background ANSI color to black.
ON_BLACK = "\e[40m"
# Set the terminal's background ANSI color to red.
ON_RED = "\e[41m"
# Set the terminal's background ANSI color to green.
ON_GREEN = "\e[42m"
# Set the terminal's background ANSI color to yellow.
ON_YELLOW = "\e[43m"
# Set the terminal's background ANSI color to blue.
ON_BLUE = "\e[44m"
# Set the terminal's background ANSI color to magenta.
ON_MAGENTA = "\e[45m"
# Set the terminal's background ANSI color to cyan.
ON_CYAN = "\e[46m"
# Set the terminal's background ANSI color to white.
ON_WHITE = "\e[47m"
# Set color by using a string or one of the defined constants. If a third
# option is set to true, it also adds bold to the string. This is based
# on Highline implementation and it automatically appends CLEAR to the end
# of the returned String.
#
# Pass foreground, background and bold options to this method as
# symbols.
#
# Example:
#
# set_color "Hi!", :red, :on_white, :bold
#
# The available colors are:
#
# :bold
# :black
# :red
# :green
# :yellow
# :blue
# :magenta
# :cyan
# :white
# :on_black
# :on_red
# :on_green
# :on_yellow
# :on_blue
# :on_magenta
# :on_cyan
# :on_white
def set_color(string, *colors)
if colors.compact.empty? || !can_display_colors?
string
elsif colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) }
ansi_colors = colors.map { |color| lookup_color(color) }
"#{ansi_colors.join}#{string}#{CLEAR}"
else
# The old API was `set_color(color, bold=boolean)`. We
# continue to support the old API because you should never
# break old APIs unnecessarily :P
foreground, bold = colors
foreground = self.class.const_get(foreground.to_s.upcase) if foreground.is_a?(Symbol)
bold = bold ? BOLD : ""
"#{bold}#{foreground}#{string}#{CLEAR}"
end
end
protected
def can_display_colors?
stdout.tty?
end
# Overwrite show_diff to show diff with colors if Diff::LCS is
# available.
#
def show_diff(destination, content) #:nodoc:
if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil?
actual = File.binread(destination).to_s.split("\n")
content = content.to_s.split("\n")
Diff::LCS.sdiff(actual, content).each do |diff|
output_diff_line(diff)
end
else
super
end
end
def output_diff_line(diff) #:nodoc:
case diff.action
when "-"
say "- #{diff.old_element.chomp}", :red, true
when "+"
say "+ #{diff.new_element.chomp}", :green, true
when "!"
say "- #{diff.old_element.chomp}", :red, true
say "+ #{diff.new_element.chomp}", :green, true
else
say " #{diff.old_element.chomp}", nil, true
end
end
# Check if Diff::LCS is loaded. If it is, use it to create pretty output
# for diff.
#
def diff_lcs_loaded? #:nodoc:
return true if defined?(Diff::LCS)
return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
@diff_lcs_loaded = begin
require "diff/lcs"
true
rescue LoadError
false
end
end
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/shell/basic.rb 0000644 0000041 0000041 00000030755 13765541231 024215 0 ustar www-data www-data require "tempfile"
require "io/console" if RUBY_VERSION > "1.9.2"
class Foreman::Thor
module Shell
class Basic
attr_accessor :base
attr_reader :padding
# Initialize base, mute and padding to nil.
#
def initialize #:nodoc:
@base = nil
@mute = false
@padding = 0
@always_force = false
end
# Mute everything that's inside given block
#
def mute
@mute = true
yield
ensure
@mute = false
end
# Check if base is muted
#
def mute?
@mute
end
# Sets the output padding, not allowing less than zero values.
#
def padding=(value)
@padding = [0, value].max
end
# Sets the output padding while executing a block and resets it.
#
def indent(count = 1)
orig_padding = padding
self.padding = padding + count
yield
self.padding = orig_padding
end
# Asks something to the user and receives a response.
#
# If asked to limit the correct responses, you can pass in an
# array of acceptable answers. If one of those is not supplied,
# they will be shown a message stating that one of those answers
# must be given and re-asked the question.
#
# If asking for sensitive information, the :echo option can be set
# to false to mask user input from $stdin.
#
# If the required input is a path, then set the path option to
# true. This will enable tab completion for file paths relative
# to the current working directory on systems that support
# Readline.
#
# ==== Example
# ask("What is your name?")
#
# ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"])
#
# ask("What is your password?", :echo => false)
#
# ask("Where should the file be saved?", :path => true)
#
def ask(statement, *args)
options = args.last.is_a?(Hash) ? args.pop : {}
color = args.first
if options[:limited_to]
ask_filtered(statement, color, options)
else
ask_simply(statement, color, options)
end
end
# Say (print) something to the user. If the sentence ends with a whitespace
# or tab character, a new line is not appended (print + flush). Otherwise
# are passed straight to puts (behavior got from Highline).
#
# ==== Example
# say("I know you knew that.")
#
def say(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/))
buffer = prepare_message(message, *color)
buffer << "\n" if force_new_line && !message.to_s.end_with?("\n")
stdout.print(buffer)
stdout.flush
end
# Say a status with the given color and appends the message. Since this
# method is used frequently by actions, it allows nil or false to be given
# in log_status, avoiding the message from being shown. If a Symbol is
# given in log_status, it's used as the color.
#
def say_status(status, message, log_status = true)
return if quiet? || log_status == false
spaces = " " * (padding + 1)
color = log_status.is_a?(Symbol) ? log_status : :green
status = status.to_s.rjust(12)
status = set_color status, color, true if color
buffer = "#{status}#{spaces}#{message}"
buffer << "\n" unless buffer.end_with?("\n")
stdout.print(buffer)
stdout.flush
end
# Make a question the to user and returns true if the user replies "y" or
# "yes".
#
def yes?(statement, color = nil)
!!(ask(statement, color, :add_to_history => false) =~ is?(:yes))
end
# Make a question the to user and returns true if the user replies "n" or
# "no".
#
def no?(statement, color = nil)
!!(ask(statement, color, :add_to_history => false) =~ is?(:no))
end
# Prints values in columns
#
# ==== Parameters
# Array[String, String, ...]
#
def print_in_columns(array)
return if array.empty?
colwidth = (array.map { |el| el.to_s.size }.max || 0) + 2
array.each_with_index do |value, index|
# Don't output trailing spaces when printing the last column
if ((((index + 1) % (terminal_width / colwidth))).zero? && !index.zero?) || index + 1 == array.length
stdout.puts value
else
stdout.printf("%-#{colwidth}s", value)
end
end
end
# Prints a table.
#
# ==== Parameters
# Array[Array[String, String, ...]]
#
# ==== Options
# indent:: Indent the first column by indent value.
# colwidth:: Force the first column to colwidth spaces wide.
#
def print_table(array, options = {}) # rubocop:disable MethodLength
return if array.empty?
formats = []
indent = options[:indent].to_i
colwidth = options[:colwidth]
options[:truncate] = terminal_width if options[:truncate] == true
formats << "%-#{colwidth + 2}s" if colwidth
start = colwidth ? 1 : 0
colcount = array.max { |a, b| a.size <=> b.size }.size
maximas = []
start.upto(colcount - 1) do |index|
maxima = array.map { |row| row[index] ? row[index].to_s.size : 0 }.max
maximas << maxima
formats << if index == colcount - 1
# Don't output 2 trailing spaces when printing the last column
"%-s"
else
"%-#{maxima + 2}s"
end
end
formats[0] = formats[0].insert(0, " " * indent)
formats << "%s"
array.each do |row|
sentence = ""
row.each_with_index do |column, index|
maxima = maximas[index]
f = if column.is_a?(Numeric)
if index == row.size - 1
# Don't output 2 trailing spaces when printing the last column
"%#{maxima}s"
else
"%#{maxima}s "
end
else
formats[index]
end
sentence << f % column.to_s
end
sentence = truncate(sentence, options[:truncate]) if options[:truncate]
stdout.puts sentence
end
end
# Prints a long string, word-wrapping the text to the current width of the
# terminal display. Ideal for printing heredocs.
#
# ==== Parameters
# String
#
# ==== Options
# indent:: Indent each line of the printed paragraph by indent value.
#
def print_wrapped(message, options = {})
indent = options[:indent] || 0
width = terminal_width - indent
paras = message.split("\n\n")
paras.map! do |unwrapped|
unwrapped.strip.tr("\n", " ").squeeze(" ").gsub(/.{1,#{width}}(?:\s|\Z)/) { ($& + 5.chr).gsub(/\n\005/, "\n").gsub(/\005/, "\n") }
end
paras.each do |para|
para.split("\n").each do |line|
stdout.puts line.insert(0, " " * indent)
end
stdout.puts unless para == paras.last
end
end
# Deals with file collision and returns true if the file should be
# overwritten and false otherwise. If a block is given, it uses the block
# response as the content for the diff.
#
# ==== Parameters
# destination:: the destination file to solve conflicts
# block:: an optional block that returns the value to be used in diff
#
def file_collision(destination)
return true if @always_force
options = block_given? ? "[Ynaqdh]" : "[Ynaqh]"
loop do
answer = ask(
%[Overwrite #{destination}? (enter "h" for help) #{options}],
:add_to_history => false
)
case answer
when is?(:yes), is?(:force), ""
return true
when is?(:no), is?(:skip)
return false
when is?(:always)
return @always_force = true
when is?(:quit)
say "Aborting..."
raise SystemExit
when is?(:diff)
show_diff(destination, yield) if block_given?
say "Retrying..."
else
say file_collision_help
end
end
end
# This code was copied from Rake, available under MIT-LICENSE
# Copyright (c) 2003, 2004 Jim Weirich
def terminal_width
result = if ENV["THOR_COLUMNS"]
ENV["THOR_COLUMNS"].to_i
else
unix? ? dynamic_width : 80
end
result < 10 ? 80 : result
rescue
80
end
# Called if something goes wrong during the execution. This is used by Foreman::Thor
# internally and should not be used inside your scripts. If something went
# wrong, you can always raise an exception. If you raise a Foreman::Thor::Error, it
# will be rescued and wrapped in the method below.
#
def error(statement)
stderr.puts statement
end
# Apply color to the given string with optional bold. Disabled in the
# Foreman::Thor::Shell::Basic class.
#
def set_color(string, *) #:nodoc:
string
end
protected
def prepare_message(message, *color)
spaces = " " * padding
spaces + set_color(message.to_s, *color)
end
def can_display_colors?
false
end
def lookup_color(color)
return color unless color.is_a?(Symbol)
self.class.const_get(color.to_s.upcase)
end
def stdout
$stdout
end
def stderr
$stderr
end
def is?(value) #:nodoc:
value = value.to_s
if value.size == 1
/\A#{value}\z/i
else
/\A(#{value}|#{value[0, 1]})\z/i
end
end
def file_collision_help #:nodoc:
<<-HELP
Y - yes, overwrite
n - no, do not overwrite
a - all, overwrite this and all others
q - quit, abort
d - diff, show the differences between the old and the new
h - help, show this help
HELP
end
def show_diff(destination, content) #:nodoc:
diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u"
Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp|
temp.write content
temp.rewind
system %(#{diff_cmd} "#{destination}" "#{temp.path}")
end
end
def quiet? #:nodoc:
mute? || (base && base.options[:quiet])
end
# Calculate the dynamic width of the terminal
def dynamic_width
@dynamic_width ||= (dynamic_width_stty.nonzero? || dynamic_width_tput)
end
def dynamic_width_stty
`stty size 2>/dev/null`.split[1].to_i
end
def dynamic_width_tput
`tput cols 2>/dev/null`.to_i
end
def unix?
RUBY_PLATFORM =~ /(aix|darwin|linux|(net|free|open)bsd|cygwin|solaris|irix|hpux)/i
end
def truncate(string, width)
as_unicode do
chars = string.chars.to_a
if chars.length <= width
chars.join
else
chars[0, width - 3].join + "..."
end
end
end
if "".respond_to?(:encode)
def as_unicode
yield
end
else
def as_unicode
old = $KCODE
$KCODE = "U"
yield
ensure
$KCODE = old
end
end
def ask_simply(statement, color, options)
default = options[:default]
message = [statement, ("(#{default})" if default), nil].uniq.join(" ")
message = prepare_message(message, *color)
result = Foreman::Thor::LineEditor.readline(message, options)
return unless result
result.strip!
if default && result == ""
default
else
result
end
end
def ask_filtered(statement, color, options)
answer_set = options[:limited_to]
correct_answer = nil
until correct_answer
answers = answer_set.join(", ")
answer = ask_simply("#{statement} [#{answers}]", color, options)
correct_answer = answer_set.include?(answer) ? answer : nil
say("Your response must be one of: [#{answers}]. Please try again.") unless correct_answer
end
correct_answer
end
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/shell/html.rb 0000644 0000041 0000041 00000010557 13765541231 024076 0 ustar www-data www-data require "foreman/vendor/thor/lib/thor/shell/basic"
class Foreman::Thor
module Shell
# Inherit from Foreman::Thor::Shell::Basic and add set_color behavior. Check
# Foreman::Thor::Shell::Basic to see all available methods.
#
class HTML < Basic
# The start of an HTML bold sequence.
BOLD = "font-weight: bold"
# Set the terminal's foreground HTML color to black.
BLACK = "color: black"
# Set the terminal's foreground HTML color to red.
RED = "color: red"
# Set the terminal's foreground HTML color to green.
GREEN = "color: green"
# Set the terminal's foreground HTML color to yellow.
YELLOW = "color: yellow"
# Set the terminal's foreground HTML color to blue.
BLUE = "color: blue"
# Set the terminal's foreground HTML color to magenta.
MAGENTA = "color: magenta"
# Set the terminal's foreground HTML color to cyan.
CYAN = "color: cyan"
# Set the terminal's foreground HTML color to white.
WHITE = "color: white"
# Set the terminal's background HTML color to black.
ON_BLACK = "background-color: black"
# Set the terminal's background HTML color to red.
ON_RED = "background-color: red"
# Set the terminal's background HTML color to green.
ON_GREEN = "background-color: green"
# Set the terminal's background HTML color to yellow.
ON_YELLOW = "background-color: yellow"
# Set the terminal's background HTML color to blue.
ON_BLUE = "background-color: blue"
# Set the terminal's background HTML color to magenta.
ON_MAGENTA = "background-color: magenta"
# Set the terminal's background HTML color to cyan.
ON_CYAN = "background-color: cyan"
# Set the terminal's background HTML color to white.
ON_WHITE = "background-color: white"
# Set color by using a string or one of the defined constants. If a third
# option is set to true, it also adds bold to the string. This is based
# on Highline implementation and it automatically appends CLEAR to the end
# of the returned String.
#
def set_color(string, *colors)
if colors.all? { |color| color.is_a?(Symbol) || color.is_a?(String) }
html_colors = colors.map { |color| lookup_color(color) }
"#{string}"
else
color, bold = colors
html_color = self.class.const_get(color.to_s.upcase) if color.is_a?(Symbol)
styles = [html_color]
styles << BOLD if bold
"#{string}"
end
end
# Ask something to the user and receives a response.
#
# ==== Example
# ask("What is your name?")
#
# TODO: Implement #ask for Foreman::Thor::Shell::HTML
def ask(statement, color = nil)
raise NotImplementedError, "Implement #ask for Foreman::Thor::Shell::HTML"
end
protected
def can_display_colors?
true
end
# Overwrite show_diff to show diff with colors if Diff::LCS is
# available.
#
def show_diff(destination, content) #:nodoc:
if diff_lcs_loaded? && ENV["THOR_DIFF"].nil? && ENV["RAILS_DIFF"].nil?
actual = File.binread(destination).to_s.split("\n")
content = content.to_s.split("\n")
Diff::LCS.sdiff(actual, content).each do |diff|
output_diff_line(diff)
end
else
super
end
end
def output_diff_line(diff) #:nodoc:
case diff.action
when "-"
say "- #{diff.old_element.chomp}", :red, true
when "+"
say "+ #{diff.new_element.chomp}", :green, true
when "!"
say "- #{diff.old_element.chomp}", :red, true
say "+ #{diff.new_element.chomp}", :green, true
else
say " #{diff.old_element.chomp}", nil, true
end
end
# Check if Diff::LCS is loaded. If it is, use it to create pretty output
# for diff.
#
def diff_lcs_loaded? #:nodoc:
return true if defined?(Diff::LCS)
return @diff_lcs_loaded unless @diff_lcs_loaded.nil?
@diff_lcs_loaded = begin
require "diff/lcs"
true
rescue LoadError
false
end
end
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/base.rb 0000644 0000041 0000041 00000055550 13765541231 022737 0 ustar www-data www-data require "foreman/vendor/thor/lib/thor/command"
require "foreman/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access"
require "foreman/vendor/thor/lib/thor/core_ext/ordered_hash"
require "foreman/vendor/thor/lib/thor/error"
require "foreman/vendor/thor/lib/thor/invocation"
require "foreman/vendor/thor/lib/thor/parser"
require "foreman/vendor/thor/lib/thor/shell"
require "foreman/vendor/thor/lib/thor/line_editor"
require "foreman/vendor/thor/lib/thor/util"
class Foreman::Thor
autoload :Actions, "foreman/vendor/thor/lib/thor/actions"
autoload :RakeCompat, "foreman/vendor/thor/lib/thor/rake_compat"
autoload :Group, "foreman/vendor/thor/lib/thor/group"
# Shortcuts for help.
HELP_MAPPINGS = %w(-h -? --help -D)
# Foreman::Thor methods that should not be overwritten by the user.
THOR_RESERVED_WORDS = %w(invoke shell options behavior root destination_root relative_root
action add_file create_file in_root inside run run_ruby_script)
TEMPLATE_EXTNAME = ".tt"
module Base
attr_accessor :options, :parent_options, :args
# It receives arguments in an Array and two hashes, one for options and
# other for configuration.
#
# Notice that it does not check if all required arguments were supplied.
# It should be done by the parser.
#
# ==== Parameters
# args:: An array of objects. The objects are applied to their
# respective accessors declared with argument.
#
# options:: An options hash that will be available as self.options.
# The hash given is converted to a hash with indifferent
# access, magic predicates (options.skip?) and then frozen.
#
# config:: Configuration for this Foreman::Thor class.
#
def initialize(args = [], local_options = {}, config = {})
parse_options = config[:current_command] && config[:current_command].disable_class_options ? {} : self.class.class_options
# The start method splits inbound arguments at the first argument
# that looks like an option (starts with - or --). It then calls
# new, passing in the two halves of the arguments Array as the
# first two parameters.
command_options = config.delete(:command_options) # hook for start
parse_options = parse_options.merge(command_options) if command_options
if local_options.is_a?(Array)
array_options = local_options
hash_options = {}
else
# Handle the case where the class was explicitly instantiated
# with pre-parsed options.
array_options = []
hash_options = local_options
end
# Let Foreman::Thor::Options parse the options first, so it can remove
# declared options from the array. This will leave us with
# a list of arguments that weren't declared.
stop_on_unknown = self.class.stop_on_unknown_option? config[:current_command]
opts = Foreman::Thor::Options.new(parse_options, hash_options, stop_on_unknown)
self.options = opts.parse(array_options)
self.options = config[:class_options].merge(options) if config[:class_options]
# If unknown options are disallowed, make sure that none of the
# remaining arguments looks like an option.
opts.check_unknown! if self.class.check_unknown_options?(config)
# Add the remaining arguments from the options parser to the
# arguments passed in to initialize. Then remove any positional
# arguments declared using #argument (this is primarily used
# by Foreman::Thor::Group). Tis will leave us with the remaining
# positional arguments.
to_parse = args
to_parse += opts.remaining unless self.class.strict_args_position?(config)
thor_args = Foreman::Thor::Arguments.new(self.class.arguments)
thor_args.parse(to_parse).each { |k, v| __send__("#{k}=", v) }
@args = thor_args.remaining
end
class << self
def included(base) #:nodoc:
base.extend ClassMethods
base.send :include, Invocation
base.send :include, Shell
end
# Returns the classes that inherits from Foreman::Thor or Foreman::Thor::Group.
#
# ==== Returns
# Array[Class]
#
def subclasses
@subclasses ||= []
end
# Returns the files where the subclasses are kept.
#
# ==== Returns
# Hash[path => Class]
#
def subclass_files
@subclass_files ||= Hash.new { |h, k| h[k] = [] }
end
# Whenever a class inherits from Foreman::Thor or Foreman::Thor::Group, we should track the
# class and the file on Foreman::Thor::Base. This is the method responsable for it.
#
def register_klass_file(klass) #:nodoc:
file = caller[1].match(/(.*):\d+/)[1]
Foreman::Thor::Base.subclasses << klass unless Foreman::Thor::Base.subclasses.include?(klass)
file_subclasses = Foreman::Thor::Base.subclass_files[File.expand_path(file)]
file_subclasses << klass unless file_subclasses.include?(klass)
end
end
module ClassMethods
def attr_reader(*) #:nodoc:
no_commands { super }
end
def attr_writer(*) #:nodoc:
no_commands { super }
end
def attr_accessor(*) #:nodoc:
no_commands { super }
end
# If you want to raise an error for unknown options, call check_unknown_options!
# This is disabled by default to allow dynamic invocations.
def check_unknown_options!
@check_unknown_options = true
end
def check_unknown_options #:nodoc:
@check_unknown_options ||= from_superclass(:check_unknown_options, false)
end
def check_unknown_options?(config) #:nodoc:
!!check_unknown_options
end
# If true, option parsing is suspended as soon as an unknown option or a
# regular argument is encountered. All remaining arguments are passed to
# the command as regular arguments.
def stop_on_unknown_option?(command_name) #:nodoc:
false
end
# If you want only strict string args (useful when cascading thor classes),
# call strict_args_position! This is disabled by default to allow dynamic
# invocations.
def strict_args_position!
@strict_args_position = true
end
def strict_args_position #:nodoc:
@strict_args_position ||= from_superclass(:strict_args_position, false)
end
def strict_args_position?(config) #:nodoc:
!!strict_args_position
end
# Adds an argument to the class and creates an attr_accessor for it.
#
# Arguments are different from options in several aspects. The first one
# is how they are parsed from the command line, arguments are retrieved
# from position:
#
# thor command NAME
#
# Instead of:
#
# thor command --name=NAME
#
# Besides, arguments are used inside your code as an accessor (self.argument),
# while options are all kept in a hash (self.options).
#
# Finally, arguments cannot have type :default or :boolean but can be
# optional (supplying :optional => :true or :required => false), although
# you cannot have a required argument after a non-required argument. If you
# try it, an error is raised.
#
# ==== Parameters
# name:: The name of the argument.
# options:: Described below.
#
# ==== Options
# :desc - Description for the argument.
# :required - If the argument is required or not.
# :optional - If the argument is optional or not.
# :type - The type of the argument, can be :string, :hash, :array, :numeric.
# :default - Default value for this argument. It cannot be required and have default values.
# :banner - String to show on usage notes.
#
# ==== Errors
# ArgumentError:: Raised if you supply a required argument after a non required one.
#
def argument(name, options = {})
is_thor_reserved_word?(name, :argument)
no_commands { attr_accessor name }
required = if options.key?(:optional)
!options[:optional]
elsif options.key?(:required)
options[:required]
else
options[:default].nil?
end
remove_argument name
if required
arguments.each do |argument|
next if argument.required?
raise ArgumentError, "You cannot have #{name.to_s.inspect} as required argument after " \
"the non-required argument #{argument.human_name.inspect}."
end
end
options[:required] = required
arguments << Foreman::Thor::Argument.new(name, options)
end
# Returns this class arguments, looking up in the ancestors chain.
#
# ==== Returns
# Array[Foreman::Thor::Argument]
#
def arguments
@arguments ||= from_superclass(:arguments, [])
end
# Adds a bunch of options to the set of class options.
#
# class_options :foo => false, :bar => :required, :baz => :string
#
# If you prefer more detailed declaration, check class_option.
#
# ==== Parameters
# Hash[Symbol => Object]
#
def class_options(options = nil)
@class_options ||= from_superclass(:class_options, {})
build_options(options, @class_options) if options
@class_options
end
# Adds an option to the set of class options
#
# ==== Parameters
# name:: The name of the argument.
# options:: Described below.
#
# ==== Options
# :desc:: -- Description for the argument.
# :required:: -- If the argument is required or not.
# :default:: -- Default value for this argument.
# :group:: -- The group for this options. Use by class options to output options in different levels.
# :aliases:: -- Aliases for this option. Note: Foreman::Thor follows a convention of one-dash-one-letter options. Thus aliases like "-something" wouldn't be parsed; use either "\--something" or "-s" instead.
# :type:: -- The type of the argument, can be :string, :hash, :array, :numeric or :boolean.
# :banner:: -- String to show on usage notes.
# :hide:: -- If you want to hide this option from the help.
#
def class_option(name, options = {})
build_option(name, options, class_options)
end
# Removes a previous defined argument. If :undefine is given, undefine
# accessors as well.
#
# ==== Parameters
# names:: Arguments to be removed
#
# ==== Examples
#
# remove_argument :foo
# remove_argument :foo, :bar, :baz, :undefine => true
#
def remove_argument(*names)
options = names.last.is_a?(Hash) ? names.pop : {}
names.each do |name|
arguments.delete_if { |a| a.name == name.to_s }
undef_method name, "#{name}=" if options[:undefine]
end
end
# Removes a previous defined class option.
#
# ==== Parameters
# names:: Class options to be removed
#
# ==== Examples
#
# remove_class_option :foo
# remove_class_option :foo, :bar, :baz
#
def remove_class_option(*names)
names.each do |name|
class_options.delete(name)
end
end
# Defines the group. This is used when thor list is invoked so you can specify
# that only commands from a pre-defined group will be shown. Defaults to standard.
#
# ==== Parameters
# name
#
def group(name = nil)
if name
@group = name.to_s
else
@group ||= from_superclass(:group, "standard")
end
end
# Returns the commands for this Foreman::Thor class.
#
# ==== Returns
# OrderedHash:: An ordered hash with commands names as keys and Foreman::Thor::Command
# objects as values.
#
def commands
@commands ||= Foreman::Thor::CoreExt::OrderedHash.new
end
alias_method :tasks, :commands
# Returns the commands for this Foreman::Thor class and all subclasses.
#
# ==== Returns
# OrderedHash:: An ordered hash with commands names as keys and Foreman::Thor::Command
# objects as values.
#
def all_commands
@all_commands ||= from_superclass(:all_commands, Foreman::Thor::CoreExt::OrderedHash.new)
@all_commands.merge!(commands)
end
alias_method :all_tasks, :all_commands
# Removes a given command from this Foreman::Thor class. This is usually done if you
# are inheriting from another class and don't want it to be available
# anymore.
#
# By default it only remove the mapping to the command. But you can supply
# :undefine => true to undefine the method from the class as well.
#
# ==== Parameters
# name:: The name of the command to be removed
# options:: You can give :undefine => true if you want commands the method
# to be undefined from the class as well.
#
def remove_command(*names)
options = names.last.is_a?(Hash) ? names.pop : {}
names.each do |name|
commands.delete(name.to_s)
all_commands.delete(name.to_s)
undef_method name if options[:undefine]
end
end
alias_method :remove_task, :remove_command
# All methods defined inside the given block are not added as commands.
#
# So you can do:
#
# class MyScript < Foreman::Thor
# no_commands do
# def this_is_not_a_command
# end
# end
# end
#
# You can also add the method and remove it from the command list:
#
# class MyScript < Foreman::Thor
# def this_is_not_a_command
# end
# remove_command :this_is_not_a_command
# end
#
def no_commands
@no_commands = true
yield
ensure
@no_commands = false
end
alias_method :no_tasks, :no_commands
# Sets the namespace for the Foreman::Thor or Foreman::Thor::Group class. By default the
# namespace is retrieved from the class name. If your Foreman::Thor class is named
# Scripts::MyScript, the help method, for example, will be called as:
#
# thor scripts:my_script -h
#
# If you change the namespace:
#
# namespace :my_scripts
#
# You change how your commands are invoked:
#
# thor my_scripts -h
#
# Finally, if you change your namespace to default:
#
# namespace :default
#
# Your commands can be invoked with a shortcut. Instead of:
#
# thor :my_command
#
def namespace(name = nil)
if name
@namespace = name.to_s
else
@namespace ||= Foreman::Thor::Util.namespace_from_thor_class(self)
end
end
# Parses the command and options from the given args, instantiate the class
# and invoke the command. This method is used when the arguments must be parsed
# from an array. If you are inside Ruby and want to use a Foreman::Thor class, you
# can simply initialize it:
#
# script = MyScript.new(args, options, config)
# script.invoke(:command, first_arg, second_arg, third_arg)
#
def start(given_args = ARGV, config = {})
config[:shell] ||= Foreman::Thor::Base.shell.new
dispatch(nil, given_args.dup, nil, config)
rescue Foreman::Thor::Error => e
config[:debug] || ENV["THOR_DEBUG"] == "1" ? (raise e) : config[:shell].error(e.message)
exit(1) if exit_on_failure?
rescue Errno::EPIPE
# This happens if a thor command is piped to something like `head`,
# which closes the pipe when it's done reading. This will also
# mean that if the pipe is closed, further unnecessary
# computation will not occur.
exit(0)
end
# Allows to use private methods from parent in child classes as commands.
#
# ==== Parameters
# names:: Method names to be used as commands
#
# ==== Examples
#
# public_command :foo
# public_command :foo, :bar, :baz
#
def public_command(*names)
names.each do |name|
class_eval "def #{name}(*); super end"
end
end
alias_method :public_task, :public_command
def handle_no_command_error(command, has_namespace = $thor_runner) #:nodoc:
raise UndefinedCommandError, "Could not find command #{command.inspect} in #{namespace.inspect} namespace." if has_namespace
raise UndefinedCommandError, "Could not find command #{command.inspect}."
end
alias_method :handle_no_task_error, :handle_no_command_error
def handle_argument_error(command, error, args, arity) #:nodoc:
msg = "ERROR: \"#{basename} #{command.name}\" was called with "
msg << "no arguments" if args.empty?
msg << "arguments " << args.inspect unless args.empty?
msg << "\nUsage: #{banner(command).inspect}"
raise InvocationError, msg
end
protected
# Prints the class options per group. If an option does not belong to
# any group, it's printed as Class option.
#
def class_options_help(shell, groups = {}) #:nodoc:
# Group options by group
class_options.each do |_, value|
groups[value.group] ||= []
groups[value.group] << value
end
# Deal with default group
global_options = groups.delete(nil) || []
print_options(shell, global_options)
# Print all others
groups.each do |group_name, options|
print_options(shell, options, group_name)
end
end
# Receives a set of options and print them.
def print_options(shell, options, group_name = nil)
return if options.empty?
list = []
padding = options.map { |o| o.aliases.size }.max.to_i * 4
options.each do |option|
next if option.hide
item = [option.usage(padding)]
item.push(option.description ? "# #{option.description}" : "")
list << item
list << ["", "# Default: #{option.default}"] if option.show_default?
list << ["", "# Possible values: #{option.enum.join(', ')}"] if option.enum
end
shell.say(group_name ? "#{group_name} options:" : "Options:")
shell.print_table(list, :indent => 2)
shell.say ""
end
# Raises an error if the word given is a Foreman::Thor reserved word.
def is_thor_reserved_word?(word, type) #:nodoc:
return false unless THOR_RESERVED_WORDS.include?(word.to_s)
raise "#{word.inspect} is a Foreman::Thor reserved word and cannot be defined as #{type}"
end
# Build an option and adds it to the given scope.
#
# ==== Parameters
# name:: The name of the argument.
# options:: Described in both class_option and method_option.
# scope:: Options hash that is being built up
def build_option(name, options, scope) #:nodoc:
scope[name] = Foreman::Thor::Option.new(name, options)
end
# Receives a hash of options, parse them and add to the scope. This is a
# fast way to set a bunch of options:
#
# build_options :foo => true, :bar => :required, :baz => :string
#
# ==== Parameters
# Hash[Symbol => Object]
def build_options(options, scope) #:nodoc:
options.each do |key, value|
scope[key] = Foreman::Thor::Option.parse(key, value)
end
end
# Finds a command with the given name. If the command belongs to the current
# class, just return it, otherwise dup it and add the fresh copy to the
# current command hash.
def find_and_refresh_command(name) #:nodoc:
if commands[name.to_s]
commands[name.to_s]
elsif command = all_commands[name.to_s] # rubocop:disable AssignmentInCondition
commands[name.to_s] = command.clone
else
raise ArgumentError, "You supplied :for => #{name.inspect}, but the command #{name.inspect} could not be found."
end
end
alias_method :find_and_refresh_task, :find_and_refresh_command
# Everytime someone inherits from a Foreman::Thor class, register the klass
# and file into baseclass.
def inherited(klass)
Foreman::Thor::Base.register_klass_file(klass)
klass.instance_variable_set(:@no_commands, false)
end
# Fire this callback whenever a method is added. Added methods are
# tracked as commands by invoking the create_command method.
def method_added(meth)
meth = meth.to_s
if meth == "initialize"
initialize_added
return
end
# Return if it's not a public instance method
return unless public_method_defined?(meth.to_sym)
@no_commands ||= false
return if @no_commands || !create_command(meth)
is_thor_reserved_word?(meth, :command)
Foreman::Thor::Base.register_klass_file(self)
end
# Retrieves a value from superclass. If it reaches the baseclass,
# returns default.
def from_superclass(method, default = nil)
if self == baseclass || !superclass.respond_to?(method, true)
default
else
value = superclass.send(method)
# Ruby implements `dup` on Object, but raises a `TypeError`
# if the method is called on immediates. As a result, we
# don't have a good way to check whether dup will succeed
# without calling it and rescuing the TypeError.
begin
value.dup
rescue TypeError
value
end
end
end
# A flag that makes the process exit with status 1 if any error happens.
def exit_on_failure?
false
end
#
# The basename of the program invoking the thor class.
#
def basename
File.basename($PROGRAM_NAME).split(" ").first
end
# SIGNATURE: Sets the baseclass. This is where the superclass lookup
# finishes.
def baseclass #:nodoc:
end
# SIGNATURE: Creates a new command if valid_command? is true. This method is
# called when a new method is added to the class.
def create_command(meth) #:nodoc:
end
alias_method :create_task, :create_command
# SIGNATURE: Defines behavior when the initialize method is added to the
# class.
def initialize_added #:nodoc:
end
# SIGNATURE: The hook invoked by start.
def dispatch(command, given_args, given_opts, config) #:nodoc:
raise NotImplementedError
end
end
end
end
foreman-0.87.2/lib/foreman/vendor/thor/lib/thor/shell.rb 0000644 0000041 0000041 00000004447 13765541231 023133 0 ustar www-data www-data require "rbconfig"
class Foreman::Thor
module Base
class << self
attr_writer :shell
# Returns the shell used in all Foreman::Thor classes. If you are in a Unix platform
# it will use a colored log, otherwise it will use a basic one without color.
#
def shell
@shell ||= if ENV["THOR_SHELL"] && !ENV["THOR_SHELL"].empty?
Foreman::Thor::Shell.const_get(ENV["THOR_SHELL"])
elsif RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ && !ENV["ANSICON"]
Foreman::Thor::Shell::Basic
else
Foreman::Thor::Shell::Color
end
end
end
end
module Shell
SHELL_DELEGATED_METHODS = [:ask, :error, :set_color, :yes?, :no?, :say, :say_status, :print_in_columns, :print_table, :print_wrapped, :file_collision, :terminal_width]
attr_writer :shell
autoload :Basic, "foreman/vendor/thor/lib/thor/shell/basic"
autoload :Color, "foreman/vendor/thor/lib/thor/shell/color"
autoload :HTML, "foreman/vendor/thor/lib/thor/shell/html"
# Add shell to initialize config values.
#
# ==== Configuration
# shell