foreman 0.37.0-mingw32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +39 -0
 - data/bin/foreman +7 -0
 - data/bin/runner +36 -0
 - data/data/example/Procfile +2 -0
 - data/data/example/Procfile.without_colon +2 -0
 - data/data/example/error +7 -0
 - data/data/example/log/neverdie.log +4 -0
 - data/data/example/ticker +14 -0
 - data/data/export/bluepill/master.pill.erb +27 -0
 - data/data/export/runit/log_run.erb +7 -0
 - data/data/export/runit/run.erb +3 -0
 - data/data/export/upstart/master.conf.erb +8 -0
 - data/data/export/upstart/process.conf.erb +5 -0
 - data/data/export/upstart/process_master.conf.erb +2 -0
 - data/lib/foreman.rb +25 -0
 - data/lib/foreman/cli.rb +98 -0
 - data/lib/foreman/distribution.rb +9 -0
 - data/lib/foreman/engine.rb +234 -0
 - data/lib/foreman/export.rb +32 -0
 - data/lib/foreman/export/base.rb +51 -0
 - data/lib/foreman/export/bluepill.rb +26 -0
 - data/lib/foreman/export/inittab.rb +36 -0
 - data/lib/foreman/export/runit.rb +59 -0
 - data/lib/foreman/export/upstart.rb +41 -0
 - data/lib/foreman/helpers.rb +45 -0
 - data/lib/foreman/process.rb +96 -0
 - data/lib/foreman/procfile.rb +38 -0
 - data/lib/foreman/procfile_entry.rb +22 -0
 - data/lib/foreman/utils.rb +18 -0
 - data/lib/foreman/version.rb +5 -0
 - data/man/foreman.1 +222 -0
 - data/spec/foreman/cli_spec.rb +163 -0
 - data/spec/foreman/engine_spec.rb +86 -0
 - data/spec/foreman/export/base_spec.rb +22 -0
 - data/spec/foreman/export/bluepill_spec.rb +36 -0
 - data/spec/foreman/export/inittab_spec.rb +40 -0
 - data/spec/foreman/export/runit_spec.rb +41 -0
 - data/spec/foreman/export/upstart_spec.rb +87 -0
 - data/spec/foreman/export_spec.rb +24 -0
 - data/spec/foreman/helpers_spec.rb +26 -0
 - data/spec/foreman/process_spec.rb +131 -0
 - data/spec/foreman_spec.rb +34 -0
 - data/spec/helper_spec.rb +18 -0
 - data/spec/resources/export/bluepill/app-concurrency.pill +47 -0
 - data/spec/resources/export/bluepill/app.pill +44 -0
 - data/spec/resources/export/inittab/inittab.concurrency +4 -0
 - data/spec/resources/export/inittab/inittab.default +4 -0
 - data/spec/resources/export/runit/app-alpha-1-log-run +7 -0
 - data/spec/resources/export/runit/app-alpha-1-run +3 -0
 - data/spec/resources/export/runit/app-alpha-2-log-run +7 -0
 - data/spec/resources/export/runit/app-alpha-2-run +3 -0
 - data/spec/resources/export/runit/app-bravo-1-log-run +7 -0
 - data/spec/resources/export/runit/app-bravo-1-run +3 -0
 - data/spec/resources/export/upstart/app-alpha-1.conf +5 -0
 - data/spec/resources/export/upstart/app-alpha-2.conf +5 -0
 - data/spec/resources/export/upstart/app-alpha.conf +2 -0
 - data/spec/resources/export/upstart/app-bravo-1.conf +5 -0
 - data/spec/resources/export/upstart/app-bravo.conf +2 -0
 - data/spec/resources/export/upstart/app.conf +8 -0
 - data/spec/spec_helper.rb +98 -0
 - metadata +138 -0
 
    
        data/README.md
    ADDED
    
    | 
         @@ -0,0 +1,39 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # Foreman
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            Manage Procfile-based applications
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            <table>
         
     | 
| 
      
 6 
     | 
    
         
            +
              <tr>
         
     | 
| 
      
 7 
     | 
    
         
            +
                <th>If you have...</th>
         
     | 
| 
      
 8 
     | 
    
         
            +
                <th>Install with...</th>
         
     | 
| 
      
 9 
     | 
    
         
            +
              </tr>
         
     | 
| 
      
 10 
     | 
    
         
            +
              <tr>
         
     | 
| 
      
 11 
     | 
    
         
            +
                <td>Ruby (MRI, JRuby, Windows)</td>
         
     | 
| 
      
 12 
     | 
    
         
            +
                <td><pre>$ gem install foreman</pre></td>
         
     | 
| 
      
 13 
     | 
    
         
            +
              </tr>
         
     | 
| 
      
 14 
     | 
    
         
            +
              <tr>
         
     | 
| 
      
 15 
     | 
    
         
            +
                <td>Mac OS X</td>
         
     | 
| 
      
 16 
     | 
    
         
            +
                <td><a href="http://assets.foreman.io/foreman/foreman.pkg">foreman.pkg</a></td>
         
     | 
| 
      
 17 
     | 
    
         
            +
              </tr>
         
     | 
| 
      
 18 
     | 
    
         
            +
            </table>
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
            ## Getting Started
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            * http://blog.daviddollar.org/2011/05/06/introducing-foreman.html
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            ## Documentation
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
            * [man page](http://ddollar.github.com/foreman)
         
     | 
| 
      
 27 
     | 
    
         
            +
            * [wiki](http://github.com/ddollar/foreman/wiki)
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
            ## Authors
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            #### Created and maintained by
         
     | 
| 
      
 32 
     | 
    
         
            +
            David Dollar
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            #### Patches contributed by
         
     | 
| 
      
 35 
     | 
    
         
            +
            Adam Wiggins, Chris Continanza, Chris Lowder, Craig R Webster, Dan Farina, Dan Peterson, David Dollar, Fletcher Nichol, Gabriel Burt, Gamaliel Toro, Greg Reinacker, Hugues Le Gendre, Hunter Nield, Iain Hecker, Jay Zeschin, Keith Rarick, Khaja Minhajuddin, Lincoln Stoll, Marcos Muino Garcia, Mark McGranaghan, Matt Griffin, Matt Haynes, Matthijs Langenberg, Michael Dwan, Michael van Rooijen, Mike Javorski, Nathan Broadbent, Nathan L Smith, Nick Zadrozny, Phil Hagelberg, Ricardo Chimal, Jr, Thom May, Tom Ward, brainopia, clifff, jc00ke
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            ## License
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
            MIT
         
     | 
    
        data/bin/foreman
    ADDED
    
    
    
        data/bin/runner
    ADDED
    
    | 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/bin/sh
         
     | 
| 
      
 2 
     | 
    
         
            +
            #
         
     | 
| 
      
 3 
     | 
    
         
            +
            #/ Usage: runner [-d <dir>] <command>
         
     | 
| 
      
 4 
     | 
    
         
            +
            #/
         
     | 
| 
      
 5 
     | 
    
         
            +
            #/ Run a command with exec, optionally changing directory first
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            set -e
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            error() {
         
     | 
| 
      
 10 
     | 
    
         
            +
              echo $@ >&2
         
     | 
| 
      
 11 
     | 
    
         
            +
              exit 1
         
     | 
| 
      
 12 
     | 
    
         
            +
            }
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            usage() {
         
     | 
| 
      
 15 
     | 
    
         
            +
              cat $0 | grep '^#/' | cut -c4-
         
     | 
| 
      
 16 
     | 
    
         
            +
              exit
         
     | 
| 
      
 17 
     | 
    
         
            +
            }
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            while getopts ":hd:" OPT; do
         
     | 
| 
      
 20 
     | 
    
         
            +
              case $OPT in
         
     | 
| 
      
 21 
     | 
    
         
            +
                 d) cd $OPTARG ;;
         
     | 
| 
      
 22 
     | 
    
         
            +
                 h) usage ;;
         
     | 
| 
      
 23 
     | 
    
         
            +
                \?) error "invalid option: -$OPTARG" ;;
         
     | 
| 
      
 24 
     | 
    
         
            +
                 :) error "option -$OPTARG requires an argument" ;;
         
     | 
| 
      
 25 
     | 
    
         
            +
              esac
         
     | 
| 
      
 26 
     | 
    
         
            +
            done
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            shift $((OPTIND-1))
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
            command=$1
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            if [ -z "$1" ]; then
         
     | 
| 
      
 33 
     | 
    
         
            +
              usage
         
     | 
| 
      
 34 
     | 
    
         
            +
            fi
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            exec $1
         
     | 
    
        data/data/example/error
    ADDED
    
    
    
        data/data/example/ticker
    ADDED
    
    
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            Bluepill.application("<%= app %>", :foreground => false, :log_file => "/var/log/bluepill.log") do |app|
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              app.uid = "<%= user %>"
         
     | 
| 
      
 4 
     | 
    
         
            +
              app.gid = "<%= user %>"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            <% engine.procfile.entries.each do |process| %>
         
     | 
| 
      
 7 
     | 
    
         
            +
            <% 1.upto(concurrency[process.name]) do |num| %>
         
     | 
| 
      
 8 
     | 
    
         
            +
            <% port = engine.port_for(process, num, self.port) %>
         
     | 
| 
      
 9 
     | 
    
         
            +
              app.process("<%= process.name %>-<%=num%>") do |process|
         
     | 
| 
      
 10 
     | 
    
         
            +
                process.start_command = "<%= process.command.gsub("$PORT", port.to_s) %>"
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                process.working_dir = "<%= engine.directory %>"
         
     | 
| 
      
 13 
     | 
    
         
            +
                process.daemonize = true
         
     | 
| 
      
 14 
     | 
    
         
            +
                process.environment = {"PORT" => "<%= port %>"}
         
     | 
| 
      
 15 
     | 
    
         
            +
                process.stop_signals = [:quit, 30.seconds, :term, 5.seconds, :kill]
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                process.stdout = process.stderr = "<%= log_root %>/<%= app %>-<%= process.name %>-<%=num%>.log"
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                process.monitor_children do |children|
         
     | 
| 
      
 20 
     | 
    
         
            +
                  children.stop_command "kill -QUIT {{PID}}"
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                process.group = "<%= app %>-<%= process.name %>"
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            <% end %>
         
     | 
| 
      
 26 
     | 
    
         
            +
            <% end %>
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,5 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            start on starting <%= app %>-<%= process.name %>
         
     | 
| 
      
 2 
     | 
    
         
            +
            stop on stopping <%= app %>-<%= process.name %>
         
     | 
| 
      
 3 
     | 
    
         
            +
            respawn
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            exec su - <%= user %> -c 'cd <%= engine.directory %>; export PORT=<%= port %>;<% engine.environment.each_pair do |var,env| %> export <%= var.upcase %>=<%= env %>; <% end %> <%= process.command %> >> <%= log_root %>/<%=process.name%>-<%=num%>.log 2>&1'
         
     | 
    
        data/lib/foreman.rb
    ADDED
    
    | 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "foreman/version"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Foreman
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              class AppDoesNotExist < Exception; end
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              # load contents of env_file into ENV
         
     | 
| 
      
 8 
     | 
    
         
            +
              def self.load_env!(env_file = './.env')
         
     | 
| 
      
 9 
     | 
    
         
            +
                require 'foreman/engine'
         
     | 
| 
      
 10 
     | 
    
         
            +
                Foreman::Engine.load_env!(env_file)
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              def self.runner
         
     | 
| 
      
 14 
     | 
    
         
            +
                File.expand_path("../../bin/runner", __FILE__)
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              def self.jruby?
         
     | 
| 
      
 18 
     | 
    
         
            +
                defined?(RUBY_PLATFORM) and RUBY_PLATFORM == "java"
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              def self.windows?
         
     | 
| 
      
 22 
     | 
    
         
            +
                defined?(RUBY_PLATFORM) and RUBY_PLATFORM =~ /(win|w)32$/
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/foreman/cli.rb
    ADDED
    
    | 
         @@ -0,0 +1,98 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "foreman"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "foreman/helpers"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "foreman/engine"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "foreman/export"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "thor"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require "yaml"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            class Foreman::CLI < Thor
         
     | 
| 
      
 9 
     | 
    
         
            +
              include Foreman::Helpers
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              desc "start", "Start the application"
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              class_option :procfile, :type => :string, :aliases => "-f", :desc => "Default: Procfile"
         
     | 
| 
      
 16 
     | 
    
         
            +
              class_option :app_root, :type => :string, :aliases => "-d", :desc => "Default: Procfile directory"
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              method_option :env,         :type => :string,  :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
         
     | 
| 
      
 19 
     | 
    
         
            +
              method_option :port,        :type => :numeric, :aliases => "-p"
         
     | 
| 
      
 20 
     | 
    
         
            +
              method_option :concurrency, :type => :string,  :aliases => "-c", :banner => '"alpha=5,bar=3"'
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 23 
     | 
    
         
            +
                # Hackery. Take the run method away from Thor so that we can redefine it.
         
     | 
| 
      
 24 
     | 
    
         
            +
                def is_thor_reserved_word?(word, type)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  return false if word == 'run'
         
     | 
| 
      
 26 
     | 
    
         
            +
                  super
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
              end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
              def start
         
     | 
| 
      
 31 
     | 
    
         
            +
                check_procfile!
         
     | 
| 
      
 32 
     | 
    
         
            +
                engine.start
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              desc "export FORMAT LOCATION", "Export the application to another process management format"
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              method_option :app,         :type => :string,  :aliases => "-a"
         
     | 
| 
      
 38 
     | 
    
         
            +
              method_option :log,         :type => :string,  :aliases => "-l"
         
     | 
| 
      
 39 
     | 
    
         
            +
              method_option :env,         :type => :string,  :aliases => "-e", :desc => "Specify an environment file to load, defaults to .env"
         
     | 
| 
      
 40 
     | 
    
         
            +
              method_option :port,        :type => :numeric, :aliases => "-p"
         
     | 
| 
      
 41 
     | 
    
         
            +
              method_option :user,        :type => :string,  :aliases => "-u"
         
     | 
| 
      
 42 
     | 
    
         
            +
              method_option :template,    :type => :string,  :aliases => "-t"
         
     | 
| 
      
 43 
     | 
    
         
            +
              method_option :concurrency, :type => :string,  :aliases => "-c", :banner => '"alpha=5,bar=3"'
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
              def export(format, location=nil)
         
     | 
| 
      
 46 
     | 
    
         
            +
                check_procfile!
         
     | 
| 
      
 47 
     | 
    
         
            +
                formatter = Foreman::Export.formatter(format)
         
     | 
| 
      
 48 
     | 
    
         
            +
                formatter.new(location, engine, options).export
         
     | 
| 
      
 49 
     | 
    
         
            +
              rescue Foreman::Export::Exception => ex
         
     | 
| 
      
 50 
     | 
    
         
            +
                error ex.message
         
     | 
| 
      
 51 
     | 
    
         
            +
              end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
              desc "check", "Validate your application's Procfile"
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
              def check
         
     | 
| 
      
 56 
     | 
    
         
            +
                error "no processes defined" unless engine.procfile.entries.length > 0
         
     | 
| 
      
 57 
     | 
    
         
            +
                puts "valid procfile detected (#{engine.procfile.process_names.join(', ')})"
         
     | 
| 
      
 58 
     | 
    
         
            +
              end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
              desc "run COMMAND", "Run a command using your application's environment"
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
              def run(*args)
         
     | 
| 
      
 63 
     | 
    
         
            +
                engine.apply_environment!
         
     | 
| 
      
 64 
     | 
    
         
            +
                begin
         
     | 
| 
      
 65 
     | 
    
         
            +
                  exec args.join(" ")
         
     | 
| 
      
 66 
     | 
    
         
            +
                rescue Errno::EACCES
         
     | 
| 
      
 67 
     | 
    
         
            +
                  error "not executable: #{args.first}"
         
     | 
| 
      
 68 
     | 
    
         
            +
                rescue Errno::ENOENT
         
     | 
| 
      
 69 
     | 
    
         
            +
                  error "command not found: #{args.first}"
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
            private ######################################################################
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
              def check_procfile!
         
     | 
| 
      
 76 
     | 
    
         
            +
                error("#{procfile} does not exist.") unless File.exist?(procfile)
         
     | 
| 
      
 77 
     | 
    
         
            +
              end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
              def engine
         
     | 
| 
      
 80 
     | 
    
         
            +
                @engine ||= Foreman::Engine.new(procfile, options)
         
     | 
| 
      
 81 
     | 
    
         
            +
              end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
      
 83 
     | 
    
         
            +
              def procfile
         
     | 
| 
      
 84 
     | 
    
         
            +
                options[:procfile] || "Procfile"
         
     | 
| 
      
 85 
     | 
    
         
            +
              end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
              def error(message)
         
     | 
| 
      
 88 
     | 
    
         
            +
                puts "ERROR: #{message}"
         
     | 
| 
      
 89 
     | 
    
         
            +
                exit 1
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              def options
         
     | 
| 
      
 93 
     | 
    
         
            +
                original_options = super
         
     | 
| 
      
 94 
     | 
    
         
            +
                return original_options unless File.exists?(".foreman")
         
     | 
| 
      
 95 
     | 
    
         
            +
                defaults = YAML::load_file(".foreman") || {}
         
     | 
| 
      
 96 
     | 
    
         
            +
                Thor::CoreExt::HashWithIndifferentAccess.new(defaults.merge(original_options))
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,234 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "foreman"
         
     | 
| 
      
 2 
     | 
    
         
            +
            require "foreman/process"
         
     | 
| 
      
 3 
     | 
    
         
            +
            require "foreman/procfile"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "foreman/utils"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "tempfile"
         
     | 
| 
      
 6 
     | 
    
         
            +
            require "timeout"
         
     | 
| 
      
 7 
     | 
    
         
            +
            require "term/ansicolor"
         
     | 
| 
      
 8 
     | 
    
         
            +
            require "fileutils"
         
     | 
| 
      
 9 
     | 
    
         
            +
            require "thread"
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            class Foreman::Engine
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              attr_reader :procfile
         
     | 
| 
      
 14 
     | 
    
         
            +
              attr_reader :directory
         
     | 
| 
      
 15 
     | 
    
         
            +
              attr_reader :options
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              extend Term::ANSIColor
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              COLORS = [ cyan, yellow, green, magenta, red, blue,
         
     | 
| 
      
 20 
     | 
    
         
            +
                         intense_cyan, intense_yellow, intense_green, intense_magenta,
         
     | 
| 
      
 21 
     | 
    
         
            +
                         intense_red, intense_blue ]
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              def initialize(procfile, options={})
         
     | 
| 
      
 24 
     | 
    
         
            +
                @procfile  = Foreman::Procfile.new(procfile)
         
     | 
| 
      
 25 
     | 
    
         
            +
                @directory = options[:app_root] || File.expand_path(File.dirname(procfile))
         
     | 
| 
      
 26 
     | 
    
         
            +
                @options = options
         
     | 
| 
      
 27 
     | 
    
         
            +
                @environment = read_environment_files(options[:env])
         
     | 
| 
      
 28 
     | 
    
         
            +
                @output_mutex = Mutex.new
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
              def self.load_env!(env_file)
         
     | 
| 
      
 32 
     | 
    
         
            +
                @environment = read_environment_files(env_file)
         
     | 
| 
      
 33 
     | 
    
         
            +
                apply_environment!
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
              def start
         
     | 
| 
      
 37 
     | 
    
         
            +
                proctitle "ruby: foreman master"
         
     | 
| 
      
 38 
     | 
    
         
            +
                termtitle "#{File.basename(@directory)} - foreman"
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                trap("TERM") { puts "SIGTERM received"; terminate_gracefully }
         
     | 
| 
      
 41 
     | 
    
         
            +
                trap("INT")  { puts "SIGINT received";  terminate_gracefully }
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                assign_colors
         
     | 
| 
      
 44 
     | 
    
         
            +
                spawn_processes
         
     | 
| 
      
 45 
     | 
    
         
            +
                watch_for_output
         
     | 
| 
      
 46 
     | 
    
         
            +
                watch_for_termination
         
     | 
| 
      
 47 
     | 
    
         
            +
              end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
              def port_for(process, num, base_port=nil)
         
     | 
| 
      
 50 
     | 
    
         
            +
                base_port ||= 5000
         
     | 
| 
      
 51 
     | 
    
         
            +
                offset = procfile.process_names.index(process.name) * 100
         
     | 
| 
      
 52 
     | 
    
         
            +
                base_port.to_i + offset + num - 1
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
            private ######################################################################
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
              def spawn_processes
         
     | 
| 
      
 58 
     | 
    
         
            +
                concurrency = Foreman::Utils.parse_concurrency(@options[:concurrency])
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                procfile.entries.each do |entry|
         
     | 
| 
      
 61 
     | 
    
         
            +
                  reader, writer = IO.pipe
         
     | 
| 
      
 62 
     | 
    
         
            +
                  entry.spawn(concurrency[entry.name], writer, @directory, @environment, port_for(entry, 1, base_port)).each do |process|
         
     | 
| 
      
 63 
     | 
    
         
            +
                    running_processes[process.pid] = process
         
     | 
| 
      
 64 
     | 
    
         
            +
                    readers[process] = reader
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
              end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
              def base_port
         
     | 
| 
      
 70 
     | 
    
         
            +
                options[:port] || 5000
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
              def kill_all(signal="SIGTERM")
         
     | 
| 
      
 74 
     | 
    
         
            +
                running_processes.each do |pid, process|
         
     | 
| 
      
 75 
     | 
    
         
            +
                  info "sending #{signal} to pid #{pid}"
         
     | 
| 
      
 76 
     | 
    
         
            +
                  process.kill signal
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
              end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
              def terminate_gracefully
         
     | 
| 
      
 81 
     | 
    
         
            +
                return if @terminating
         
     | 
| 
      
 82 
     | 
    
         
            +
                @terminating = true
         
     | 
| 
      
 83 
     | 
    
         
            +
                info "sending SIGTERM to all processes"
         
     | 
| 
      
 84 
     | 
    
         
            +
                kill_all "SIGTERM"
         
     | 
| 
      
 85 
     | 
    
         
            +
                Timeout.timeout(5) do
         
     | 
| 
      
 86 
     | 
    
         
            +
                  while running_processes.length > 0
         
     | 
| 
      
 87 
     | 
    
         
            +
                    pid, status = Process.wait2
         
     | 
| 
      
 88 
     | 
    
         
            +
                    process = running_processes.delete(pid)
         
     | 
| 
      
 89 
     | 
    
         
            +
                    info "process terminated", process.name
         
     | 
| 
      
 90 
     | 
    
         
            +
                  end
         
     | 
| 
      
 91 
     | 
    
         
            +
                end
         
     | 
| 
      
 92 
     | 
    
         
            +
              rescue Timeout::Error
         
     | 
| 
      
 93 
     | 
    
         
            +
                info "sending SIGKILL to all processes"
         
     | 
| 
      
 94 
     | 
    
         
            +
                kill_all "SIGKILL"
         
     | 
| 
      
 95 
     | 
    
         
            +
              end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
              def watch_for_output
         
     | 
| 
      
 98 
     | 
    
         
            +
                Thread.new do
         
     | 
| 
      
 99 
     | 
    
         
            +
                  require "win32console" if Foreman.windows?
         
     | 
| 
      
 100 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 101 
     | 
    
         
            +
                    loop do
         
     | 
| 
      
 102 
     | 
    
         
            +
                      rs, ws = IO.select(readers.values, [], [], 1)
         
     | 
| 
      
 103 
     | 
    
         
            +
                      (rs || []).each do |r|
         
     | 
| 
      
 104 
     | 
    
         
            +
                        data = r.gets
         
     | 
| 
      
 105 
     | 
    
         
            +
                        next unless data
         
     | 
| 
      
 106 
     | 
    
         
            +
                        ps, message = data.split(",", 2)
         
     | 
| 
      
 107 
     | 
    
         
            +
                        color = colors[ps.split(".").first]
         
     | 
| 
      
 108 
     | 
    
         
            +
                        info message, ps, color
         
     | 
| 
      
 109 
     | 
    
         
            +
                      end
         
     | 
| 
      
 110 
     | 
    
         
            +
                    end
         
     | 
| 
      
 111 
     | 
    
         
            +
                  rescue Exception => ex
         
     | 
| 
      
 112 
     | 
    
         
            +
                    puts ex.message
         
     | 
| 
      
 113 
     | 
    
         
            +
                    puts ex.backtrace
         
     | 
| 
      
 114 
     | 
    
         
            +
                  end
         
     | 
| 
      
 115 
     | 
    
         
            +
                end
         
     | 
| 
      
 116 
     | 
    
         
            +
              end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
              def watch_for_termination
         
     | 
| 
      
 119 
     | 
    
         
            +
                pid, status = Process.wait2
         
     | 
| 
      
 120 
     | 
    
         
            +
                process = running_processes.delete(pid)
         
     | 
| 
      
 121 
     | 
    
         
            +
                info "process terminated", process.name
         
     | 
| 
      
 122 
     | 
    
         
            +
                terminate_gracefully
         
     | 
| 
      
 123 
     | 
    
         
            +
              rescue Errno::ECHILD
         
     | 
| 
      
 124 
     | 
    
         
            +
              end
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
              def info(message, name="system", color=Term::ANSIColor.white)
         
     | 
| 
      
 127 
     | 
    
         
            +
                output  = ""
         
     | 
| 
      
 128 
     | 
    
         
            +
                output += color
         
     | 
| 
      
 129 
     | 
    
         
            +
                output += "#{Time.now.strftime("%H:%M:%S")} #{pad_process_name(name)} | "
         
     | 
| 
      
 130 
     | 
    
         
            +
                output += Term::ANSIColor.reset
         
     | 
| 
      
 131 
     | 
    
         
            +
                output += message.chomp
         
     | 
| 
      
 132 
     | 
    
         
            +
                puts output
         
     | 
| 
      
 133 
     | 
    
         
            +
              end
         
     | 
| 
      
 134 
     | 
    
         
            +
             
     | 
| 
      
 135 
     | 
    
         
            +
              def print(message=nil)
         
     | 
| 
      
 136 
     | 
    
         
            +
                @output_mutex.synchronize do
         
     | 
| 
      
 137 
     | 
    
         
            +
                  $stdout.print message
         
     | 
| 
      
 138 
     | 
    
         
            +
                end
         
     | 
| 
      
 139 
     | 
    
         
            +
              end
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
              def puts(message=nil)
         
     | 
| 
      
 142 
     | 
    
         
            +
                @output_mutex.synchronize do
         
     | 
| 
      
 143 
     | 
    
         
            +
                  $stdout.puts message
         
     | 
| 
      
 144 
     | 
    
         
            +
                end
         
     | 
| 
      
 145 
     | 
    
         
            +
              end
         
     | 
| 
      
 146 
     | 
    
         
            +
             
     | 
| 
      
 147 
     | 
    
         
            +
              def longest_process_name
         
     | 
| 
      
 148 
     | 
    
         
            +
                @longest_process_name ||= begin
         
     | 
| 
      
 149 
     | 
    
         
            +
                  longest = procfile.process_names.map { |name| name.length }.sort.last
         
     | 
| 
      
 150 
     | 
    
         
            +
                  longest = 6 if longest < 6 # system
         
     | 
| 
      
 151 
     | 
    
         
            +
                  longest
         
     | 
| 
      
 152 
     | 
    
         
            +
                end
         
     | 
| 
      
 153 
     | 
    
         
            +
              end
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
              def pad_process_name(name="system")
         
     | 
| 
      
 156 
     | 
    
         
            +
                name.to_s.ljust(longest_process_name + 3) # add 3 for process number padding
         
     | 
| 
      
 157 
     | 
    
         
            +
              end
         
     | 
| 
      
 158 
     | 
    
         
            +
             
     | 
| 
      
 159 
     | 
    
         
            +
              def proctitle(title)
         
     | 
| 
      
 160 
     | 
    
         
            +
                $0 = title
         
     | 
| 
      
 161 
     | 
    
         
            +
              end
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
              def termtitle(title)
         
     | 
| 
      
 164 
     | 
    
         
            +
                printf("\033]0;#{title}\007") unless Foreman.windows?
         
     | 
| 
      
 165 
     | 
    
         
            +
              end
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
              def running_processes
         
     | 
| 
      
 168 
     | 
    
         
            +
                @running_processes ||= {}
         
     | 
| 
      
 169 
     | 
    
         
            +
              end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
              def readers
         
     | 
| 
      
 172 
     | 
    
         
            +
                @readers ||= {}
         
     | 
| 
      
 173 
     | 
    
         
            +
              end
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
              def colors
         
     | 
| 
      
 176 
     | 
    
         
            +
                @colors ||= {}
         
     | 
| 
      
 177 
     | 
    
         
            +
              end
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
              def assign_colors
         
     | 
| 
      
 180 
     | 
    
         
            +
                procfile.entries.each do |entry|
         
     | 
| 
      
 181 
     | 
    
         
            +
                  colors[entry.name] = next_color
         
     | 
| 
      
 182 
     | 
    
         
            +
                end
         
     | 
| 
      
 183 
     | 
    
         
            +
              end
         
     | 
| 
      
 184 
     | 
    
         
            +
             
     | 
| 
      
 185 
     | 
    
         
            +
              def process_by_reader(reader)
         
     | 
| 
      
 186 
     | 
    
         
            +
                readers.invert[reader]
         
     | 
| 
      
 187 
     | 
    
         
            +
              end
         
     | 
| 
      
 188 
     | 
    
         
            +
             
     | 
| 
      
 189 
     | 
    
         
            +
              def next_color
         
     | 
| 
      
 190 
     | 
    
         
            +
                @current_color ||= -1
         
     | 
| 
      
 191 
     | 
    
         
            +
                @current_color  +=  1
         
     | 
| 
      
 192 
     | 
    
         
            +
                @current_color = 0 if COLORS.length < @current_color
         
     | 
| 
      
 193 
     | 
    
         
            +
                COLORS[@current_color]
         
     | 
| 
      
 194 
     | 
    
         
            +
              end
         
     | 
| 
      
 195 
     | 
    
         
            +
             
     | 
| 
      
 196 
     | 
    
         
            +
              module Env
         
     | 
| 
      
 197 
     | 
    
         
            +
                attr_reader :environment
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
                def read_environment_files(filenames)
         
     | 
| 
      
 200 
     | 
    
         
            +
                  environment = {}
         
     | 
| 
      
 201 
     | 
    
         
            +
             
     | 
| 
      
 202 
     | 
    
         
            +
                  (filenames || "").split(",").map(&:strip).each do |filename|
         
     | 
| 
      
 203 
     | 
    
         
            +
                    error "No such file: #{filename}" unless File.exists?(filename)
         
     | 
| 
      
 204 
     | 
    
         
            +
                    environment.merge!(read_environment(filename))
         
     | 
| 
      
 205 
     | 
    
         
            +
                  end
         
     | 
| 
      
 206 
     | 
    
         
            +
             
     | 
| 
      
 207 
     | 
    
         
            +
                  environment.merge!(read_environment(".env")) unless filenames
         
     | 
| 
      
 208 
     | 
    
         
            +
                  environment
         
     | 
| 
      
 209 
     | 
    
         
            +
                end
         
     | 
| 
      
 210 
     | 
    
         
            +
             
     | 
| 
      
 211 
     | 
    
         
            +
                def read_environment(filename)
         
     | 
| 
      
 212 
     | 
    
         
            +
                  return {} unless File.exists?(filename)
         
     | 
| 
      
 213 
     | 
    
         
            +
             
     | 
| 
      
 214 
     | 
    
         
            +
                  File.read(filename).split("\n").inject({}) do |hash, line|
         
     | 
| 
      
 215 
     | 
    
         
            +
                    if line =~ /\A([A-Za-z_0-9]+)=(.*)\z/
         
     | 
| 
      
 216 
     | 
    
         
            +
                      hash[$1] = $2
         
     | 
| 
      
 217 
     | 
    
         
            +
                    end
         
     | 
| 
      
 218 
     | 
    
         
            +
                    hash
         
     | 
| 
      
 219 
     | 
    
         
            +
                  end
         
     | 
| 
      
 220 
     | 
    
         
            +
                end
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                def apply_environment!
         
     | 
| 
      
 223 
     | 
    
         
            +
                  @environment.each { |k,v| ENV[k] = v }
         
     | 
| 
      
 224 
     | 
    
         
            +
                end
         
     | 
| 
      
 225 
     | 
    
         
            +
             
     | 
| 
      
 226 
     | 
    
         
            +
                def error(message)
         
     | 
| 
      
 227 
     | 
    
         
            +
                  puts "ERROR: #{message}"
         
     | 
| 
      
 228 
     | 
    
         
            +
                  exit 1
         
     | 
| 
      
 229 
     | 
    
         
            +
                end
         
     | 
| 
      
 230 
     | 
    
         
            +
              end
         
     | 
| 
      
 231 
     | 
    
         
            +
             
     | 
| 
      
 232 
     | 
    
         
            +
              include Env
         
     | 
| 
      
 233 
     | 
    
         
            +
              extend  Env
         
     | 
| 
      
 234 
     | 
    
         
            +
            end
         
     |