foreman-0.90.0/0000755000004100000410000000000015043264261013262 5ustar www-datawww-dataforeman-0.90.0/data/0000755000004100000410000000000015043264261014173 5ustar www-datawww-dataforeman-0.90.0/data/example/0000755000004100000410000000000015043264261015626 5ustar www-datawww-dataforeman-0.90.0/data/example/utf80000755000004100000410000000036415043264261016445 0ustar www-datawww-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.90.0/data/example/Procfile0000644000004100000410000000013315043264261017311 0ustar www-datawww-dataticker: ruby ./ticker $PORT error: ruby ./error utf8: ruby ./utf8 spawner: ./spawner foreman-0.90.0/data/example/spawnee0000755000004100000410000000021615043264261017215 0ustar www-datawww-data#!/bin/sh NAME="$1" sigterm() { echo "$NAME: got sigterm" } #trap sigterm SIGTERM while true; do echo "$NAME: ping $$" sleep 1 done foreman-0.90.0/data/example/ticker0000755000004100000410000000035515043264261017040 0ustar www-datawww-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.90.0/data/example/error0000755000004100000410000000013115043264261016700 0ustar www-datawww-data#!/usr/bin/env ruby $stdout.sync = true puts "will error in 10s" sleep 5 raise "Dying" foreman-0.90.0/data/example/Procfile.without_colon0000644000004100000410000000004415043264261022206 0ustar www-datawww-dataticker ./ticker $PORT error ./errorforeman-0.90.0/data/example/spawner0000755000004100000410000000007315043264261017233 0ustar www-datawww-data#!/bin/sh ./spawnee A & ./spawnee B & ./spawnee C & wait foreman-0.90.0/data/example/log/0000755000004100000410000000000015043264261016407 5ustar www-datawww-dataforeman-0.90.0/data/example/log/neverdie.log0000644000004100000410000000010215043264261020704 0ustar www-datawww-datatick tick ./never_die:6:in `sleep': Interrupt from ./never_die:6 foreman-0.90.0/data/export/0000755000004100000410000000000015043264261015514 5ustar www-datawww-dataforeman-0.90.0/data/export/launchd/0000755000004100000410000000000015043264261017132 5ustar www-datawww-dataforeman-0.90.0/data/export/launchd/launchd.plist.erb0000644000004100000410000000200515043264261022371 0ustar www-datawww-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 -%> KeepAlive RunAtLoad StandardOutPath <%= log %>/<%= app %>-<%= name %>-<%=num%>.log StandardErrorPath <%= log %>/<%= app %>-<%= name %>-<%=num%>.log UserName <%= user %> WorkingDirectory <%= engine.root %> foreman-0.90.0/data/export/supervisord/0000755000004100000410000000000015043264261020101 5ustar www-datawww-dataforeman-0.90.0/data/export/supervisord/app.conf.erb0000644000004100000410000000146615043264261022306 0ustar www-datawww-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.90.0/data/export/systemd/0000755000004100000410000000000015043264261017204 5ustar www-datawww-dataforeman-0.90.0/data/export/systemd/process.service.erb0000644000004100000410000000102515043264261023011 0ustar www-datawww-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.90.0/data/export/systemd/master.target.erb0000644000004100000410000000012215043264261022451 0ustar www-datawww-data[Unit] Wants=<%= service_names.join(' ') %> [Install] WantedBy=multi-user.target foreman-0.90.0/data/export/daemon/0000755000004100000410000000000015043264261016757 5ustar www-datawww-dataforeman-0.90.0/data/export/daemon/process.conf.erb0000644000004100000410000000072315043264261022055 0ustar www-datawww-datastart 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.90.0/data/export/daemon/process_master.conf.erb0000644000004100000410000000007115043264261023424 0ustar www-datawww-datastart on starting <%= app %> stop on stopping <%= app %> foreman-0.90.0/data/export/daemon/master.conf.erb0000644000004100000410000000031215043264261021664 0ustar www-datawww-datapre-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.90.0/data/export/upstart/0000755000004100000410000000000015043264261017216 5ustar www-datawww-dataforeman-0.90.0/data/export/upstart/process.conf.erb0000644000004100000410000000050515043264261022312 0ustar www-datawww-datastart 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.90.0/data/export/upstart/process_master.conf.erb0000644000004100000410000000007115043264261023663 0ustar www-datawww-datastart on starting <%= app %> stop on stopping <%= app %> foreman-0.90.0/data/export/upstart/master.conf.erb0000644000004100000410000000006215043264261022125 0ustar www-datawww-datastart on runlevel [2345] stop on runlevel [!2345] foreman-0.90.0/data/export/runit/0000755000004100000410000000000015043264261016655 5ustar www-datawww-dataforeman-0.90.0/data/export/runit/run.erb0000644000004100000410000000022615043264261020153 0ustar www-datawww-data#!/bin/sh cd <%= engine.root %> exec 2>&1 exec chpst -u <%= user %> -e <%= File.join(location, "#{process_directory}/env") %> <%= process.command %> foreman-0.90.0/data/export/runit/log/0000755000004100000410000000000015043264261017436 5ustar www-datawww-dataforeman-0.90.0/data/export/runit/log/run.erb0000644000004100000410000000024715043264261020737 0ustar www-datawww-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.90.0/data/export/bluepill/0000755000004100000410000000000015043264261017324 5ustar www-datawww-dataforeman-0.90.0/data/export/bluepill/master.pill.erb0000644000004100000410000000172315043264261022253 0ustar www-datawww-dataBluepill.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).map { |k,v| "#{k.inspect}=>#{v.inspect}" }.join(",") %>} 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.90.0/bin/0000755000004100000410000000000015043264261014032 5ustar www-datawww-dataforeman-0.90.0/bin/foreman0000755000004100000410000000016315043264261015407 0ustar www-datawww-data#!/usr/bin/env ruby $:.unshift File.expand_path("../../lib", __FILE__) require "foreman/cli" Foreman::CLI.start foreman-0.90.0/bin/foreman-runner0000755000004100000410000000116115043264261016715 0ustar www-datawww-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.90.0/lib/0000755000004100000410000000000015043264261014030 5ustar www-datawww-dataforeman-0.90.0/lib/foreman/0000755000004100000410000000000015043264261015457 5ustar www-datawww-dataforeman-0.90.0/lib/foreman/export.rb0000644000004100000410000000177315043264261017335 0ustar www-datawww-datarequire "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.90.0/lib/foreman/procfile.rb0000644000004100000410000000375415043264261017620 0ustar www-datawww-datarequire "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 EmptyFileError = Class.new(StandardError) # 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) parse_data = parse(filename) raise EmptyFileError if parse_data.empty? @entries.replace parse_data 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.90.0/lib/foreman/process.rb0000644000004100000410000000365615043264261017474 0ustar www-datawww-datarequire "foreman" require "shellwords" class Foreman::Process attr_reader :command attr_reader :env # Create a Process # # @param [String] command The command to run # @param [Hash] options # # @option options [String] :cwd (./) Change to this working directory before executing the process # @option options [Hash] :env ({}) Environment variables to set for this process # def initialize(command, options={}) @command = command @options = options.dup @options[:env] ||= {} end # Get environment-expanded command for a +Process+ # # @param [Hash] custom_env ({}) Environment variables to merge with defaults # # @return [String] The expanded command # def expanded_command(custom_env={}) env = @options[:env].merge(custom_env) expanded_command = command.dup env.each do |key, val| expanded_command.gsub!("$#{key}", val) end expanded_command end # Run a +Process+ # # @param [Hash] options # # @option options :env ({}) Environment variables to set for this execution # @option options :output ($stdout) The output stream # # @returns [Fixnum] pid The +pid+ of the process # def run(options={}) env = @options[:env].merge(options[:env] || {}) output = options[:output] || $stdout runner = "#{Foreman.runner}".shellescape Dir.chdir(cwd) do Process.spawn env, expanded_command(env), :out => output, :err => output end end # Exec a +Process+ # # @param [Hash] options # # @option options :env ({}) Environment variables to set for this execution # # @return Does not return def exec(options={}) env = @options[:env].merge(options[:env] || {}) env.each { |k, v| ENV[k] = v } Dir.chdir(cwd) Kernel.exec expanded_command(env) end # Returns the working directory for this +Process+ # # @returns [String] # def cwd File.expand_path(@options[:cwd] || ".") end end foreman-0.90.0/lib/foreman/engine.rb0000644000004100000410000002773015043264261017262 0ustar www-datawww-datarequire "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.90.0/lib/foreman/helpers.rb0000644000004100000410000000244015043264261017446 0ustar www-datawww-datamodule 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.90.0/lib/foreman/distribution.rb0000644000004100000410000000030415043264261020520 0ustar www-datawww-datamodule Foreman module Distribution def self.files Dir[File.expand_path("../../../{bin,data,lib}/**/*", __FILE__)].select do |file| File.file?(file) end end end end foreman-0.90.0/lib/foreman/cli.rb0000644000004100000410000001313615043264261016557 0ustar www-datawww-datarequire "foreman" require "foreman/helpers" require "foreman/engine" require "foreman/engine/cli" require "foreman/export" require "foreman/version" require "shellwords" require "thor" require "yaml" class Foreman::CLI < Thor include Foreman::Helpers map ["-v", "--version"] => :version class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile" class_option :root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory" desc "start [PROCESS]", "Start the application (or a specific PROCESS)" method_option :color, :type => :boolean, :aliases => "-c", :desc => "Force color to be enabled" method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env" method_option :formation, :type => :string, :aliases => "-m", :banner => '"alpha=5,bar=3"', :desc => 'Specify what processes will run and how many. Default: "all=1"' method_option :port, :type => :numeric, :aliases => "-p" method_option :timeout, :type => :numeric, :aliases => "-t", :desc => "Specify the amount of time (in seconds) processes have to shutdown gracefully before receiving a SIGKILL, defaults to 5." method_option :timestamp, :type => :boolean, :default => true, :desc => "Include timestamp in output" class << self def exit_on_failure? true end # Hackery. Take the run method away from Thor so that we can redefine it. def is_thor_reserved_word?(word, type) return false if word == "run" super end end def start(process=nil) check_procfile! load_environment! engine.load_procfile(procfile) engine.options[:formation] = "#{process}=1" if process engine.start rescue Foreman::Procfile::EmptyFileError error "no processes defined" end desc "export FORMAT LOCATION", "Export the application to another process management format" method_option :app, :type => :string, :aliases => "-a" method_option :log, :type => :string, :aliases => "-l" method_option :run, :type => :string, :aliases => "-r", :desc => "Specify the pid file directory, defaults to /var/run/" method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env" method_option :port, :type => :numeric, :aliases => "-p" method_option :user, :type => :string, :aliases => "-u" method_option :template, :type => :string, :aliases => "-t" method_option :formation, :type => :string, :aliases => "-m", :banner => '"alpha=5,bar=3"', :desc => 'Specify what processes will run and how many. Default: "all=1"' method_option :timeout, :type => :numeric, :aliases => "-t", :desc => "Specify the amount of time (in seconds) processes have to shutdown gracefully before receiving a SIGKILL, defaults to 5." def export(format, location=nil) check_procfile! load_environment! engine.load_procfile(procfile) formatter = Foreman::Export.formatter(format) formatter.new(location, engine, options).export rescue Foreman::Export::Exception, Foreman::Procfile::EmptyFileError => ex error ex.message end desc "check", "Validate your application's Procfile" def check check_procfile! engine.load_procfile(procfile) puts "valid procfile detected (#{engine.process_names.join(', ')})" rescue Foreman::Procfile::EmptyFileError error "no processes defined" end desc "run COMMAND [ARGS...]", "Run a command using your application's environment" method_option :env, :type => :string, :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env" stop_on_unknown_option! :run def run(*args) load_environment! if File.file?(procfile) engine.load_procfile(procfile) end pid = fork do begin engine.env.each { |k,v| ENV[k] = v } if args.size == 1 && process = engine.process(args.first) process.exec(:env => engine.env) else exec args.shelljoin end rescue Errno::EACCES error "not executable: #{args.first}" rescue Errno::ENOENT error "command not found: #{args.first}" end end trap("INT") do Process.kill(:INT, pid) end Process.wait(pid) exit $?.exitstatus || 0 rescue Interrupt rescue Foreman::Procfile::EmptyFileError error "no processes defined" end desc "version", "Display Foreman gem version" def version puts Foreman::VERSION end no_tasks do def engine @engine ||= begin engine_class = Foreman::Engine::CLI engine = engine_class.new(options) engine end end def options original_options = super return original_options unless File.file?(".foreman") defaults = ::YAML::load_file(".foreman") || {} Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options)) end end private ###################################################################### def error(message) puts "ERROR: #{message}" exit 1 end def check_procfile! error("#{procfile} does not exist.") unless File.file?(procfile) end def load_environment! if options[:env] options[:env].split(",").each do |file| engine.load_env file end else default_env = File.join(engine.root, ".env") engine.load_env default_env if File.file?(default_env) end end def procfile case when options[:procfile] then options[:procfile] when options[:root] then File.expand_path(File.join(options[:root], "Procfile")) else "Procfile" end end end foreman-0.90.0/lib/foreman/export/0000755000004100000410000000000015043264261017000 5ustar www-datawww-dataforeman-0.90.0/lib/foreman/export/launchd.rb0000644000004100000410000000102215043264261020736 0ustar www-datawww-datarequire "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.90.0/lib/foreman/export/systemd.rb0000644000004100000410000000160215043264261021014 0ustar www-datawww-datarequire "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.90.0/lib/foreman/export/runit.rb0000644000004100000410000000165615043264261020476 0ustar www-datawww-datarequire "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.90.0/lib/foreman/export/supervisord.rb0000644000004100000410000000043115043264261021710 0ustar www-datawww-datarequire "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.90.0/lib/foreman/export/base.rb0000644000004100000410000000656115043264261020247 0ustar www-datawww-datarequire "foreman/export" 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 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 chown user, dir FileUtils.chown user, nil, dir rescue error("Could not chown #{dir} to #{user}") unless File.writable?(dir) || ! File.exist?(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.exist?(filename) say "cleaning up: #{filename}" FileUtils.rm(filename) end def clean_dir(dirname) return unless File.exist?(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.exist?(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.90.0/lib/foreman/export/upstart.rb0000644000004100000410000000202515043264261021026 0ustar www-datawww-datarequire "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.90.0/lib/foreman/export/inittab.rb0000644000004100000410000000213615043264261020761 0ustar www-datawww-datarequire "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.90.0/lib/foreman/export/bluepill.rb0000644000004100000410000000035515043264261021140 0ustar www-datawww-datarequire "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.90.0/lib/foreman/export/daemon.rb0000644000004100000410000000150615043264261020572 0ustar www-datawww-datarequire "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.90.0/lib/foreman/engine/0000755000004100000410000000000015043264261016724 5ustar www-datawww-dataforeman-0.90.0/lib/foreman/engine/cli.rb0000644000004100000410000000470615043264261020027 0ustar www-datawww-datarequire "foreman/engine" class Foreman::Engine::CLI < Foreman::Engine module Color ANSI = { :reset => 0, :black => 30, :red => 31, :green => 32, :yellow => 33, :blue => 34, :magenta => 35, :cyan => 36, :white => 37, :bright_black => 30, :bright_red => 31, :bright_green => 32, :bright_yellow => 33, :bright_blue => 34, :bright_magenta => 35, :bright_cyan => 36, :bright_white => 37, } def self.enable(io, force=false) io.extend(self) @@color_force = force end def color? return true if @@color_force return false if Foreman.windows? return false unless self.respond_to?(:isatty) self.isatty && ENV["TERM"] end def color(name) return "" unless color? return "" unless ansi = ANSI[name.to_sym] "\e[#{ansi}m" end end FOREMAN_COLORS = %w( cyan yellow green magenta red blue bright_cyan bright_yellow bright_green bright_magenta bright_red bright_blue ) def startup @colors = map_colors proctitle "foreman: main" unless Foreman.windows? Color.enable($stdout, options[:color]) end def output(name, data) data.to_s.lines.map(&:chomp).each do |message| output = "" output += $stdout.color(@colors[name.split(".").first].to_sym) output += "#{Time.now.strftime("%H:%M:%S")} " if options[:timestamp] output += "#{pad_process_name(name)} | " output += $stdout.color(:reset) output += message $stdout.puts output $stdout.flush end rescue Errno::EPIPE terminate_gracefully end def shutdown end private def name_padding @name_padding ||= begin index_padding = @names.values.map { |n| formation[n] }.max.to_s.length + 1 name_padding = @names.values.map { |n| n.length + index_padding }.sort.last [ 6, name_padding.to_i ].max end end def pad_process_name(name) name.ljust(name_padding, " ") end def map_colors colors = Hash.new("white") @names.values.each_with_index do |name, index| colors[name] = FOREMAN_COLORS[index % FOREMAN_COLORS.length] end colors["system"] = "bright_white" colors end def proctitle(title) $0 = title end def termtitle(title) printf("\033]0;#{title}\007") unless Foreman.windows? end end foreman-0.90.0/lib/foreman/version.rb0000644000004100000410000000005215043264261017466 0ustar www-datawww-datamodule Foreman VERSION = "0.90.0" end foreman-0.90.0/lib/foreman/env.rb0000644000004100000410000000124015043264261016571 0ustar www-datawww-datarequire "foreman" class Foreman::Env attr_reader :entries def initialize(filename) @entries = File.read(filename).gsub("\r\n","\n").split("\n").inject({}) do |ax, line| if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/ key = $1 case val = $2 # Remove single quotes when /\A'(.*)'\z/ then ax[key] = $1 # Remove double quotes and unescape string preserving newline characters when /\A"(.*)"\z/ then ax[key] = $1.gsub('\n', "\n").gsub(/\\(.)/, '\1') else ax[key] = val end end ax end end def entries @entries.each do |key, value| yield key, value end end end foreman-0.90.0/lib/foreman.rb0000644000004100000410000000046315043264261016007 0ustar www-datawww-datarequire "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.90.0/spec/0000755000004100000410000000000015043264261014214 5ustar www-datawww-dataforeman-0.90.0/spec/spec_helper.rb0000644000004100000410000000666515043264261017047 0ustar www-datawww-datarequire "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.call }.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 end foreman-0.90.0/spec/foreman/0000755000004100000410000000000015043264261015643 5ustar www-datawww-dataforeman-0.90.0/spec/foreman/procfile_spec.rb0000644000004100000410000000346215043264261021012 0ustar www-datawww-datarequire '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 "raises an error if Procfile is empty" do write_file "Procfile" do |procfile| procfile.puts end expect { Foreman::Procfile.new("Procfile") }.to raise_error described_class::EmptyFileError 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.90.0/spec/foreman/engine_spec.rb0000644000004100000410000000640415043264261020453 0ustar www-datawww-datarequire "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.90.0/spec/foreman/export_spec.rb0000644000004100000410000000136415043264261020527 0ustar www-datawww-datarequire "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.90.0/spec/foreman/cli_spec.rb0000644000004100000410000000702015043264261017750 0ustar www-datawww-datarequire "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 -f #{resource_path("Procfile")} echo 1")).to eq("1\n") end it "doesn't parse options for the command" do expect(forked_foreman("run -f #{resource_path("Procfile")} grep -e FOO #{resource_path(".env")}")).to eq("FOO=bar\n") end it "includes the environment" do expect(forked_foreman("run -f #{resource_path("Procfile")} -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 -f #{resource_path("Procfile")} echo 1")).to eq(0) expect(fork_and_get_exitstatus("run -f #{resource_path("Procfile")} 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.90.0/spec/foreman/process_spec.rb0000644000004100000410000000430115043264261020656 0ustar www-datawww-datarequire 'spec_helper' require 'foreman/process' 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.90.0/spec/foreman/export/0000755000004100000410000000000015043264261017164 5ustar www-datawww-dataforeman-0.90.0/spec/foreman/export/launchd_spec.rb0000644000004100000410000000217615043264261022147 0ustar www-datawww-datarequire "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.90.0/spec/foreman/export/runit_spec.rb0000644000004100000410000000346415043264261021673 0ustar www-datawww-datarequire "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.90.0/spec/foreman/export/systemd_spec.rb0000644000004100000410000001444315043264261022221 0ustar www-datawww-datarequire "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.90.0/spec/foreman/export/inittab_spec.rb0000644000004100000410000000233115043264261022154 0ustar www-datawww-datarequire "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.90.0/spec/foreman/export/supervisord_spec.rb0000644000004100000410000000250215043264261023107 0ustar www-datawww-datarequire "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.90.0/spec/foreman/export/daemon_spec.rb0000644000004100000410000000731215043264261021771 0ustar www-datawww-datarequire "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.exist?("/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.90.0/spec/foreman/export/bluepill_spec.rb0000644000004100000410000000234515043264261022337 0ustar www-datawww-datarequire "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.90.0/spec/foreman/export/base_spec.rb0000644000004100000410000000125215043264261021435 0ustar www-datawww-datarequire "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.90.0/spec/foreman/export/upstart_spec.rb0000644000004100000410000001062515043264261022231 0ustar www-datawww-datarequire "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.exist?("/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.90.0/spec/foreman/helpers_spec.rb0000644000004100000410000000106415043264261020645 0ustar www-datawww-datarequire "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.90.0/spec/helper_spec.rb0000644000004100000410000000070215043264261017031 0ustar www-datawww-datarequire "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.90.0/spec/resources/0000755000004100000410000000000015043264261016226 5ustar www-datawww-dataforeman-0.90.0/spec/resources/bin/0000755000004100000410000000000015043264261016776 5ustar www-datawww-dataforeman-0.90.0/spec/resources/bin/utf80000755000004100000410000000004415043264261017610 0ustar www-datawww-data#!/usr/bin/env ruby puts "\xff\x03" foreman-0.90.0/spec/resources/bin/echo0000755000004100000410000000002215043264261017634 0ustar www-datawww-data#!/bin/sh echo $* foreman-0.90.0/spec/resources/bin/env0000755000004100000410000000002715043264261017513 0ustar www-datawww-data#!/bin/bash echo ${!1} foreman-0.90.0/spec/resources/bin/test0000755000004100000410000000003115043264261017675 0ustar www-datawww-data#!/bin/sh echo "testing" foreman-0.90.0/spec/resources/Procfile0000644000004100000410000000015115043264261017711 0ustar www-datawww-dataecho: bin/echo echoing env: bin/env FOO test: bin/test utf8: bin/utf8 ps: bin/echo PS env var is $PS foreman-0.90.0/spec/resources/export/0000755000004100000410000000000015043264261017547 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/launchd/0000755000004100000410000000000015043264261021165 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/launchd/launchd-b.default0000644000004100000410000000143215043264261024370 0ustar www-datawww-data Label app-bravo-1 EnvironmentVariables PORT 5100 ProgramArguments ./bravo KeepAlive RunAtLoad StandardOutPath /var/log/app/app-bravo-1.log StandardErrorPath /var/log/app/app-bravo-1.log UserName app WorkingDirectory /tmp/app foreman-0.90.0/spec/resources/export/launchd/launchd-a.default0000644000004100000410000000143215043264261024367 0ustar www-datawww-data Label app-alpha-1 EnvironmentVariables PORT 5000 ProgramArguments ./alpha KeepAlive RunAtLoad StandardOutPath /var/log/app/app-alpha-1.log StandardErrorPath /var/log/app/app-alpha-1.log UserName app WorkingDirectory /tmp/app foreman-0.90.0/spec/resources/export/launchd/launchd-c.default0000644000004100000410000000147315043264261024376 0ustar www-datawww-data Label app-alpha-1 EnvironmentVariables PORT 5000 ProgramArguments ./alpha charlie KeepAlive RunAtLoad StandardOutPath /var/log/app/app-alpha-1.log StandardErrorPath /var/log/app/app-alpha-1.log UserName app WorkingDirectory /tmp/app foreman-0.90.0/spec/resources/export/supervisord/0000755000004100000410000000000015043264261022134 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/supervisord/app-alpha-1.conf0000644000004100000410000000216115043264261025004 0ustar www-datawww-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.90.0/spec/resources/export/supervisord/app-alpha-2.conf0000644000004100000410000000071715043264261025012 0ustar www-datawww-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.90.0/spec/resources/export/inittab/0000755000004100000410000000000015043264261021201 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/inittab/inittab.default0000644000004100000410000000076415043264261024210 0ustar www-datawww-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.90.0/spec/resources/export/inittab/inittab.concurrency0000644000004100000410000000043415043264261025110 0ustar www-datawww-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.90.0/spec/resources/export/systemd/0000755000004100000410000000000015043264261021237 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/systemd/app-alpha.target0000644000004100000410000000003115043264261024304 0ustar www-datawww-data[Unit] PartOf=app.target foreman-0.90.0/spec/resources/export/systemd/app-bravo.target0000644000004100000410000000003115043264261024330 0ustar www-datawww-data[Unit] PartOf=app.target foreman-0.90.0/spec/resources/export/systemd/app-alpha.2.service0000644000004100000410000000052115043264261024622 0ustar www-datawww-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.90.0/spec/resources/export/systemd/app.target0000644000004100000410000000020715043264261023226 0ustar www-datawww-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.90.0/spec/resources/export/systemd/app-alpha.1.service0000644000004100000410000000052115043264261024621 0ustar www-datawww-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.90.0/spec/resources/export/systemd/app-bravo.1.service0000644000004100000410000000052115043264261024645 0ustar www-datawww-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.90.0/spec/resources/export/daemon/0000755000004100000410000000000015043264261021012 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/daemon/app-bravo.conf0000644000004100000410000000005315043264261023546 0ustar www-datawww-datastart on starting app stop on stopping app foreman-0.90.0/spec/resources/export/daemon/app-alpha-1.conf0000644000004100000410000000036515043264261023666 0ustar www-datawww-datastart 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.90.0/spec/resources/export/daemon/app-alpha-2.conf0000644000004100000410000000036515043264261023667 0ustar www-datawww-datastart 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.90.0/spec/resources/export/daemon/app.conf0000644000004100000410000000030215043264261022434 0ustar www-datawww-datapre-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.90.0/spec/resources/export/daemon/app-alpha.conf0000644000004100000410000000005315043264261023522 0ustar www-datawww-datastart on starting app stop on stopping app foreman-0.90.0/spec/resources/export/daemon/app-bravo-1.conf0000644000004100000410000000036515043264261023712 0ustar www-datawww-datastart 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.90.0/spec/resources/export/upstart/0000755000004100000410000000000015043264261021251 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/upstart/app-bravo.conf0000644000004100000410000000005315043264261024005 0ustar www-datawww-datastart on starting app stop on stopping app foreman-0.90.0/spec/resources/export/upstart/app-alpha-1.conf0000644000004100000410000000017015043264261024117 0ustar www-datawww-datastart on starting app-alpha stop on stopping app-alpha respawn env PORT=5000 setuid app chdir /tmp/app exec ./alpha foreman-0.90.0/spec/resources/export/upstart/app-alpha-2.conf0000644000004100000410000000017015043264261024120 0ustar www-datawww-datastart on starting app-alpha stop on stopping app-alpha respawn env PORT=5001 setuid app chdir /tmp/app exec ./alpha foreman-0.90.0/spec/resources/export/upstart/app.conf0000644000004100000410000000006215043264261022676 0ustar www-datawww-datastart on runlevel [2345] stop on runlevel [!2345] foreman-0.90.0/spec/resources/export/upstart/app-alpha.conf0000644000004100000410000000005315043264261023761 0ustar www-datawww-datastart on starting app stop on stopping app foreman-0.90.0/spec/resources/export/upstart/app-bravo-1.conf0000644000004100000410000000017015043264261024143 0ustar www-datawww-datastart on starting app-bravo stop on stopping app-bravo respawn env PORT=5100 setuid app chdir /tmp/app exec ./bravo foreman-0.90.0/spec/resources/export/runit/0000755000004100000410000000000015043264261020710 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/runit/app-alpha-1/0000755000004100000410000000000015043264261022711 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/runit/app-alpha-1/run0000644000004100000410000000013715043264261023441 0ustar www-datawww-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.90.0/spec/resources/export/runit/app-alpha-1/log/0000755000004100000410000000000015043264261023472 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/runit/app-alpha-1/log/run0000644000004100000410000000021215043264261024214 0ustar www-datawww-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.90.0/spec/resources/export/runit/app-alpha-2/0000755000004100000410000000000015043264261022712 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/runit/app-alpha-2/run0000644000004100000410000000013715043264261023442 0ustar www-datawww-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.90.0/spec/resources/export/runit/app-alpha-2/log/0000755000004100000410000000000015043264261023473 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/runit/app-alpha-2/log/run0000644000004100000410000000021215043264261024215 0ustar www-datawww-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.90.0/spec/resources/export/runit/app-bravo-1/0000755000004100000410000000000015043264261022735 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/runit/app-bravo-1/run0000644000004100000410000000012715043264261023464 0ustar www-datawww-data#!/bin/sh cd /tmp/app exec 2>&1 exec chpst -u app -e /tmp/init/app-bravo-1/env ./bravo foreman-0.90.0/spec/resources/export/runit/app-bravo-1/log/0000755000004100000410000000000015043264261023516 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/runit/app-bravo-1/log/run0000644000004100000410000000021215043264261024240 0ustar www-datawww-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.90.0/spec/resources/export/bluepill/0000755000004100000410000000000015043264261021357 5ustar www-datawww-dataforeman-0.90.0/spec/resources/export/bluepill/app.pill0000644000004100000410000000420415043264261023021 0ustar www-datawww-dataBluepill.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.90.0/spec/resources/export/bluepill/app-concurrency.pill0000644000004100000410000000221615043264261025352 0ustar www-datawww-dataBluepill.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.90.0/spec/resources/Procfile.bad0000644000004100000410000000003115043264261020433 0ustar www-datawww-datagood: sleep 1 bad: false foreman-0.90.0/spec/foreman_spec.rb0000644000004100000410000000043415043264261017203 0ustar www-datawww-datarequire "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.exist?(Foreman.runner)).to eq(true) end end end foreman-0.90.0/man/0000755000004100000410000000000015043264261014035 5ustar www-datawww-dataforeman-0.90.0/man/foreman.10000644000004100000410000001447015043264261015554 0ustar www-datawww-data.\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 .TH "FOREMAN" "1" "July 2025" "Foreman 0.90.0" "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.90.0/foreman.gemspec0000644000004100000410000001302115043264261016253 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: foreman 0.90.0 ruby lib Gem::Specification.new do |s| s.name = "foreman".freeze s.version = "0.90.0" 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 = "1980-01-02" 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/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 = "https://github.com/ddollar/foreman".freeze s.licenses = ["MIT".freeze] s.rubygems_version = "3.3.15".freeze s.summary = "Process manager for applications with multiple components".freeze if s.respond_to? :specification_version then s.specification_version = 4 end if s.respond_to? :add_runtime_dependency then s.add_runtime_dependency(%q.freeze, ["~> 1.4"]) else s.add_dependency(%q.freeze, ["~> 1.4"]) end end foreman-0.90.0/README.md0000644000004100000410000000317615043264261014550 0ustar www-datawww-data# Foreman [![CI](https://github.com/ddollar/foreman/actions/workflows/ci.yml/badge.svg)](https://github.com/ddollar/foreman/actions/workflows/ci.yml) 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 [ci.yml](.github/workflows/ci.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/main/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.