procodile 1.0.2 → 1.0.3
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.
- checksums.yaml +4 -4
- data/bin/procodile +17 -35
- data/lib/procodile/cli.rb +176 -42
- data/lib/procodile/config.rb +48 -22
- data/lib/procodile/control_session.rb +19 -9
- data/lib/procodile/instance.rb +26 -7
- data/lib/procodile/status_cli_output.rb +77 -0
- data/lib/procodile/supervisor.rb +76 -30
- data/lib/procodile/version.rb +3 -0
- metadata +4 -4
- data/lib/procodile/capistrano2.rb +0 -39
- data/lib/procodile/capistrano3.rb +0 -40
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 812b0f566074632d4c15caddfd02e6b1ddf8ee88
         | 
| 4 | 
            +
              data.tar.gz: 2acf4f380545089801e036b22aebbd1937d5be6b
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 38ee04794ce4b93d537f20704cae020dd3f03e5af610f181a3a2c11a1657e5fc48dc486ad759d74cd60187e8859869176dd8d19df1f3ef6bcd7d01ca2e8bd11e
         | 
| 7 | 
            +
              data.tar.gz: 57ff3aef3f9e8dcbbe3c0871f68e15e82b1fa18c10e035d891042774340388bf2de595bc1de95e6343e3b5f743e9ebd13a126c9db105efa925ddaaa8c034e1ba
         | 
    
        data/bin/procodile
    CHANGED
    
    | @@ -1,43 +1,29 @@ | |
| 1 1 | 
             
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            $:.unshift(File.expand_path('../../lib', __FILE__))
         | 
| 3 | 
            +
             | 
| 2 4 | 
             
            require 'optparse'
         | 
| 3 5 | 
             
            require 'fileutils'
         | 
| 4 | 
            -
            $:.unshift(File.expand_path('../../lib', __FILE__))
         | 
| 5 6 | 
             
            require 'procodile'
         | 
| 7 | 
            +
            require 'procodile/version'
         | 
| 8 | 
            +
            require 'procodile/error'
         | 
| 9 | 
            +
            require 'procodile/config'
         | 
| 10 | 
            +
            require 'procodile/cli'
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            Thread.abort_on_exception = true
         | 
| 6 13 |  | 
| 7 | 
            -
            command = ARGV[0]
         | 
| 14 | 
            +
            command = ARGV[0] || 'help'
         | 
| 15 | 
            +
            cli = Procodile::CLI.new
         | 
| 8 16 |  | 
| 9 17 | 
             
            options = {}
         | 
| 10 18 | 
             
            begin
         | 
| 11 19 | 
             
              OptionParser.new do |opts|
         | 
| 12 | 
            -
                opts. | 
| 20 | 
            +
                opts.version = Procodile::VERSION
         | 
| 21 | 
            +
                opts.banner = "Usage: procodile #{command} [options]"
         | 
| 13 22 | 
             
                opts.on("-r", "--root PATH", "The path to the root of your application") do |root|
         | 
| 14 23 | 
             
                  options[:root] = root
         | 
| 15 24 | 
             
                end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
                  opts.on("-p", "--processes a,b,c", "Only #{command} the listed processes or process types") do |processes|
         | 
| 19 | 
            -
                    options[:processes] = processes
         | 
| 20 | 
            -
                  end
         | 
| 21 | 
            -
                end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                if command == 'start'
         | 
| 24 | 
            -
                  opts.on("-f", "--foreground", "Run the supervisor in the foreground") do
         | 
| 25 | 
            -
                    options[:foreground] = true
         | 
| 26 | 
            -
                  end
         | 
| 27 | 
            -
             | 
| 28 | 
            -
                  opts.on("--clean", "Remove all previous pid and sock files before starting") do
         | 
| 29 | 
            -
                    options[:clean] = true
         | 
| 30 | 
            -
                  end
         | 
| 31 | 
            -
             | 
| 32 | 
            -
                  opts.on("-b", "--brittle", "Kill everything when one process exits") do
         | 
| 33 | 
            -
                    options[:brittle] = true
         | 
| 34 | 
            -
                  end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                  opts.on("-d", "--dev", "Run in development mode") do
         | 
| 37 | 
            -
                    options[:development] = true
         | 
| 38 | 
            -
                    options[:brittle] = true
         | 
| 39 | 
            -
                    options[:foreground] = true
         | 
| 40 | 
            -
                  end
         | 
| 25 | 
            +
                if cli.class.commands[command.to_sym] && option_block = cli.class.commands[command.to_sym][:options]
         | 
| 26 | 
            +
                  option_block.call(opts, cli)
         | 
| 41 27 | 
             
                end
         | 
| 42 28 | 
             
              end.parse!
         | 
| 43 29 | 
             
            rescue OptionParser::InvalidOption => e
         | 
| @@ -45,14 +31,10 @@ rescue OptionParser::InvalidOption => e | |
| 45 31 | 
             
              exit 1
         | 
| 46 32 | 
             
            end
         | 
| 47 33 |  | 
| 48 | 
            -
            require 'procodile/error'
         | 
| 49 | 
            -
            require 'procodile/config'
         | 
| 50 | 
            -
            require 'procodile/cli'
         | 
| 51 | 
            -
             | 
| 52 | 
            -
            Thread.abort_on_exception = true
         | 
| 53 34 | 
             
            begin
         | 
| 54 | 
            -
               | 
| 55 | 
            -
             | 
| 35 | 
            +
              if command != 'help'
         | 
| 36 | 
            +
                cli.config = Procodile::Config.new(options[:root] ? File.expand_path(options[:root]) : FileUtils.pwd)
         | 
| 37 | 
            +
              end
         | 
| 56 38 | 
             
              cli.run(command)
         | 
| 57 39 | 
             
            rescue Procodile::Error => e
         | 
| 58 40 | 
             
              $stderr.puts "Error: #{e.message}".color(31)
         | 
    
        data/lib/procodile/cli.rb
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            require 'fileutils'
         | 
| 2 | 
            +
            require 'procodile/version'
         | 
| 2 3 | 
             
            require 'procodile/error'
         | 
| 3 4 | 
             
            require 'procodile/supervisor'
         | 
| 4 5 | 
             
            require 'procodile/signal_handler'
         | 
| @@ -7,20 +8,93 @@ require 'procodile/control_client' | |
| 7 8 | 
             
            module Procodile
         | 
| 8 9 | 
             
              class CLI
         | 
| 9 10 |  | 
| 10 | 
            -
                def  | 
| 11 | 
            -
                  @ | 
| 12 | 
            -
             | 
| 11 | 
            +
                def self.commands
         | 
| 12 | 
            +
                  @commands ||= {}
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                def self.desc(description)
         | 
| 16 | 
            +
                  @description = description
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def self.options(&block)
         | 
| 20 | 
            +
                  @options = block
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def self.command(name)
         | 
| 24 | 
            +
                  commands[name] = {:name => name, :description => @description, :options => @options}
         | 
| 25 | 
            +
                  @description = nil
         | 
| 26 | 
            +
                  @options = nil
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                attr_accessor :options
         | 
| 30 | 
            +
                attr_accessor :config
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def initialize
         | 
| 33 | 
            +
                  @options = {}
         | 
| 13 34 | 
             
                end
         | 
| 14 35 |  | 
| 15 36 | 
             
                def run(command)
         | 
| 16 | 
            -
                  if self.class. | 
| 37 | 
            +
                  if self.class.commands.keys.include?(command.to_sym)
         | 
| 17 38 | 
             
                    public_send(command)
         | 
| 18 39 | 
             
                  else
         | 
| 19 40 | 
             
                    raise Error, "Invalid command '#{command}'"
         | 
| 20 41 | 
             
                  end
         | 
| 21 42 | 
             
                end
         | 
| 22 43 |  | 
| 23 | 
            -
                 | 
| 44 | 
            +
                #
         | 
| 45 | 
            +
                # Help
         | 
| 46 | 
            +
                #
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                desc "Shows this help output"
         | 
| 49 | 
            +
                command def help
         | 
| 50 | 
            +
                  puts "\e[45;37mWelcome to Procodile v#{Procodile::VERSION}\e[0m"
         | 
| 51 | 
            +
                  puts "For documentation see https://adam.ac/procodile."
         | 
| 52 | 
            +
                  puts
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                  puts "The following commands are supported:"
         | 
| 55 | 
            +
                  puts
         | 
| 56 | 
            +
                  self.class.commands.each do |method, options|
         | 
| 57 | 
            +
                    puts "  \e[34m#{method.to_s.ljust(18, ' ')}\e[0m #{options[:description]}"
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
                  puts
         | 
| 60 | 
            +
                  puts "For details for the options available for each command, use the --help option."
         | 
| 61 | 
            +
                  puts "For example 'procodile start --help'."
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                #
         | 
| 65 | 
            +
                # Start
         | 
| 66 | 
            +
                #
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                desc "Starts processes and/or the supervisor"
         | 
| 69 | 
            +
                options do |opts, cli|
         | 
| 70 | 
            +
                  opts.on("-p", "--processes a,b,c", "Only start the listed processes or process types") do |processes|
         | 
| 71 | 
            +
                    cli.options[:processes] = processes
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                  opts.on("-f", "--foreground", "Run the supervisor in the foreground") do
         | 
| 75 | 
            +
                    cli.options[:foreground] = true
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  opts.on("--clean", "Remove all previous pid and sock files before starting") do
         | 
| 79 | 
            +
                    cli.options[:clean] = true
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                  opts.on("-b", "--brittle", "Kill everything when one process exits") do
         | 
| 83 | 
            +
                    cli.options[:brittle] = true
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                  opts.on("--stop-when-none", "Stop the supervisor when all processes are stopped") do
         | 
| 87 | 
            +
                    cli.options[:stop_when_none] = true
         | 
| 88 | 
            +
                  end
         | 
| 89 | 
            +
             | 
| 90 | 
            +
                  opts.on("-d", "--dev", "Run in development mode") do
         | 
| 91 | 
            +
                    cli.options[:development] = true
         | 
| 92 | 
            +
                    cli.options[:brittle] = true
         | 
| 93 | 
            +
                    cli.options[:foreground] = true
         | 
| 94 | 
            +
                    cli.options[:stop_when_none] = true
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
                command def start
         | 
| 24 98 | 
             
                  if running?
         | 
| 25 99 | 
             
                    instances = ControlClient.run(@config.sock_path, 'start_processes', :processes => process_names_from_cli_option)
         | 
| 26 100 | 
             
                    if instances.empty?
         | 
| @@ -34,17 +108,18 @@ module Procodile | |
| 34 108 | 
             
                  end
         | 
| 35 109 |  | 
| 36 110 | 
             
                  run_options = {}
         | 
| 37 | 
            -
                  run_options[:brittle] = @ | 
| 111 | 
            +
                  run_options[:brittle] = @options[:brittle]
         | 
| 112 | 
            +
                  run_options[:stop_when_none] = @options[:stop_when_none]
         | 
| 38 113 |  | 
| 39 114 | 
             
                  processes = process_names_from_cli_option
         | 
| 40 115 |  | 
| 41 | 
            -
                  if @ | 
| 116 | 
            +
                  if @options[:clean]
         | 
| 42 117 | 
             
                    FileUtils.rm_f(File.join(@config.pid_root, '*.pid'))
         | 
| 43 118 | 
             
                    FileUtils.rm_f(File.join(@config.pid_root, '*.sock'))
         | 
| 44 119 | 
             
                    puts "Removed all old pid & sock files"
         | 
| 45 120 | 
             
                  end
         | 
| 46 121 |  | 
| 47 | 
            -
                  if @ | 
| 122 | 
            +
                  if @options[:foreground]
         | 
| 48 123 | 
             
                    File.open(pid_path, 'w') { |f| f.write(::Process.pid) }
         | 
| 49 124 | 
             
                    Supervisor.new(@config, run_options).start(:processes => processes)
         | 
| 50 125 | 
             
                  else
         | 
| @@ -62,10 +137,24 @@ module Procodile | |
| 62 137 | 
             
                  end
         | 
| 63 138 | 
             
                end
         | 
| 64 139 |  | 
| 65 | 
            -
                 | 
| 140 | 
            +
                #
         | 
| 141 | 
            +
                # Stop
         | 
| 142 | 
            +
                #
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                desc "Stops processes and/or the supervisor"
         | 
| 145 | 
            +
                options do |opts, cli|
         | 
| 146 | 
            +
                  opts.on("-p", "--processes a,b,c", "Only stop the listed processes or process types") do |processes|
         | 
| 147 | 
            +
                    cli.options[:processes] = processes
         | 
| 148 | 
            +
                  end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                  opts.on("-s", "--stop-supervisor", "Stop the ") do
         | 
| 151 | 
            +
                    cli.options[:stop_supervisor] = true
         | 
| 152 | 
            +
                  end
         | 
| 153 | 
            +
                end
         | 
| 154 | 
            +
                command def stop
         | 
| 66 155 | 
             
                  if running?
         | 
| 67 156 | 
             
                    options = {}
         | 
| 68 | 
            -
                    instances = ControlClient.run(@config.sock_path, 'stop', :processes => process_names_from_cli_option)
         | 
| 157 | 
            +
                    instances = ControlClient.run(@config.sock_path, 'stop', :processes => process_names_from_cli_option, :stop_supervisor => @options[:stop_supervisor])
         | 
| 69 158 | 
             
                    if instances.empty?
         | 
| 70 159 | 
             
                      puts "There are no processes to stop."
         | 
| 71 160 | 
             
                    else
         | 
| @@ -78,7 +167,17 @@ module Procodile | |
| 78 167 | 
             
                  end
         | 
| 79 168 | 
             
                end
         | 
| 80 169 |  | 
| 81 | 
            -
                 | 
| 170 | 
            +
                #
         | 
| 171 | 
            +
                # Restart
         | 
| 172 | 
            +
                #
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                desc "Restart processes"
         | 
| 175 | 
            +
                options do |opts, cli|
         | 
| 176 | 
            +
                  opts.on("-p", "--processes a,b,c", "Only restart the listed processes or process types") do |processes|
         | 
| 177 | 
            +
                    cli.options[:processes] = processes
         | 
| 178 | 
            +
                  end
         | 
| 179 | 
            +
                end
         | 
| 180 | 
            +
                command def restart
         | 
| 82 181 | 
             
                  if running?
         | 
| 83 182 | 
             
                    options = {}
         | 
| 84 183 | 
             
                    instances = ControlClient.run(@config.sock_path, 'restart', :processes => process_names_from_cli_option)
         | 
| @@ -94,7 +193,12 @@ module Procodile | |
| 94 193 | 
             
                  end
         | 
| 95 194 | 
             
                end
         | 
| 96 195 |  | 
| 97 | 
            -
                 | 
| 196 | 
            +
                #
         | 
| 197 | 
            +
                # Stop Supervisor
         | 
| 198 | 
            +
                #
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                desc "Stop the supervisor without stopping processes"
         | 
| 201 | 
            +
                command def stop_supervisor
         | 
| 98 202 | 
             
                  if running?
         | 
| 99 203 | 
             
                    ::Process.kill('TERM', current_pid)
         | 
| 100 204 | 
             
                    puts "Supervisor will be stopped in a moment."
         | 
| @@ -103,49 +207,79 @@ module Procodile | |
| 103 207 | 
             
                  end
         | 
| 104 208 | 
             
                end
         | 
| 105 209 |  | 
| 106 | 
            -
                 | 
| 210 | 
            +
                #
         | 
| 211 | 
            +
                # Reload Config
         | 
| 212 | 
            +
                #
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                desc "Reload Procodile configuration"
         | 
| 215 | 
            +
                command def reload
         | 
| 107 216 | 
             
                  if running?
         | 
| 108 217 | 
             
                    ControlClient.run(@config.sock_path, 'reload_config')
         | 
| 109 | 
            -
                    puts " | 
| 218 | 
            +
                    puts "Reloaded config for #{@config.app_name}"
         | 
| 110 219 | 
             
                  else
         | 
| 111 220 | 
             
                    raise Error, "#{@config.app_name} supervisor isn't running"
         | 
| 112 221 | 
             
                  end
         | 
| 113 222 | 
             
                end
         | 
| 114 223 |  | 
| 115 | 
            -
                 | 
| 224 | 
            +
                #
         | 
| 225 | 
            +
                # Check process concurrency
         | 
| 226 | 
            +
                #
         | 
| 227 | 
            +
             | 
| 228 | 
            +
                desc "Check process concurrency"
         | 
| 229 | 
            +
                options do |opts, cli|
         | 
| 230 | 
            +
                  opts.on("--no-reload", "Do not reload the configuration before checking") do |processes|
         | 
| 231 | 
            +
                    cli.options[:reload] = false
         | 
| 232 | 
            +
                  end
         | 
| 233 | 
            +
                end
         | 
| 234 | 
            +
                command def check_concurrency
         | 
| 116 235 | 
             
                  if running?
         | 
| 117 | 
            -
                     | 
| 118 | 
            -
                     | 
| 119 | 
            -
                      puts  | 
| 120 | 
            -
             | 
| 121 | 
            -
                       | 
| 122 | 
            -
             | 
| 123 | 
            -
                       | 
| 124 | 
            -
             | 
| 125 | 
            -
                       | 
| 126 | 
            -
             | 
| 127 | 
            -
                      if instances.empty?
         | 
| 128 | 
            -
                        puts "||".color(process['log_color']) + " No processes running."
         | 
| 129 | 
            -
                      else
         | 
| 130 | 
            -
                        instances.each do |instance|
         | 
| 131 | 
            -
                          print "|| ".color(process['log_color']) + instance['description'].to_s.ljust(20, ' ').color(process['log_color'])
         | 
| 132 | 
            -
                          if instance['running']
         | 
| 133 | 
            -
                            print ' Running '.color("42;37")
         | 
| 134 | 
            -
                          else
         | 
| 135 | 
            -
                            print ' Stopped '.color("41;37")
         | 
| 136 | 
            -
                          end
         | 
| 137 | 
            -
                          print "     pid " + instance['pid'].to_s.ljust(12, ' ')
         | 
| 138 | 
            -
                          print instance['respawns'].to_s + " respawns"
         | 
| 139 | 
            -
                          puts
         | 
| 140 | 
            -
                        end
         | 
| 236 | 
            +
                    reply = ControlClient.run(@config.sock_path, 'check_concurrency', :reload => @options[:reload])
         | 
| 237 | 
            +
                    if reply['started'].empty? && reply['stopped'].empty?
         | 
| 238 | 
            +
                      puts "Everything looks good!"
         | 
| 239 | 
            +
                    else
         | 
| 240 | 
            +
                      reply['started'].each do |instance|
         | 
| 241 | 
            +
                        puts "Started #{instance['description']}".color(32)
         | 
| 242 | 
            +
                      end
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                      reply['stopped'].each do |instance|
         | 
| 245 | 
            +
                        puts "Stopped #{instance['description']}".color(31)
         | 
| 141 246 | 
             
                      end
         | 
| 142 247 | 
             
                    end
         | 
| 248 | 
            +
                  else
         | 
| 249 | 
            +
                    raise Error, "#{@config.app_name} supervisor isn't running"
         | 
| 250 | 
            +
                  end
         | 
| 251 | 
            +
                end
         | 
| 252 | 
            +
             | 
| 253 | 
            +
                #
         | 
| 254 | 
            +
                # Status
         | 
| 255 | 
            +
                #
         | 
| 256 | 
            +
             | 
| 257 | 
            +
                desc "Show the current status of processes"
         | 
| 258 | 
            +
                options do |opts, cli|
         | 
| 259 | 
            +
                  opts.on("--json", "Return the status as a JSON hash") do
         | 
| 260 | 
            +
                    cli.options[:json] = true
         | 
| 261 | 
            +
                  end
         | 
| 262 | 
            +
                end
         | 
| 263 | 
            +
                command def status
         | 
| 264 | 
            +
                  if running?
         | 
| 265 | 
            +
                    status = ControlClient.run(@config.sock_path, 'status')
         | 
| 266 | 
            +
                    if @options[:json]
         | 
| 267 | 
            +
                      puts status.to_json
         | 
| 268 | 
            +
                    else
         | 
| 269 | 
            +
                      require 'procodile/status_cli_output'
         | 
| 270 | 
            +
                      StatusCLIOutput.new(status).print_all
         | 
| 271 | 
            +
                    end
         | 
| 143 272 | 
             
                  else
         | 
| 144 273 | 
             
                    puts "#{@config.app_name} supervisor not running"
         | 
| 145 274 | 
             
                  end
         | 
| 146 275 | 
             
                end
         | 
| 147 276 |  | 
| 148 | 
            -
                 | 
| 277 | 
            +
                #
         | 
| 278 | 
            +
                # Kill
         | 
| 279 | 
            +
                #
         | 
| 280 | 
            +
             | 
| 281 | 
            +
                desc "Forcefully kill all known processes"
         | 
| 282 | 
            +
                command def kill
         | 
| 149 283 | 
             
                  Dir[File.join(@config.pid_root, '*.pid')].each do |pid_path|
         | 
| 150 284 | 
             
                    name = pid_path.split('/').last.gsub(/\.pid\z/, '')
         | 
| 151 285 | 
             
                    pid = File.read(pid_path).to_i
         | 
| @@ -195,8 +329,8 @@ module Procodile | |
| 195 329 | 
             
                end
         | 
| 196 330 |  | 
| 197 331 | 
             
                def process_names_from_cli_option
         | 
| 198 | 
            -
                  if @ | 
| 199 | 
            -
                    processes = @ | 
| 332 | 
            +
                  if @options[:processes]
         | 
| 333 | 
            +
                    processes = @options[:processes].split(',')
         | 
| 200 334 | 
             
                    if processes.empty?
         | 
| 201 335 | 
             
                      raise Error, "No process names provided"
         | 
| 202 336 | 
             
                    end
         | 
    
        data/lib/procodile/config.rb
    CHANGED
    
    | @@ -21,37 +21,33 @@ module Procodile | |
| 21 21 | 
             
                  @process_list = nil
         | 
| 22 22 | 
             
                  @options = nil
         | 
| 23 23 | 
             
                  @process_options = nil
         | 
| 24 | 
            -
             | 
| 25 | 
            -
                   | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
                         | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
                         | 
| 24 | 
            +
                  @local_options = nil
         | 
| 25 | 
            +
                  @local_process_options = nil
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  if @processes
         | 
| 28 | 
            +
                    process_list.each do |name, command|
         | 
| 29 | 
            +
                      if process = @processes[name]
         | 
| 30 | 
            +
                        # This command is already in our list. Add it.
         | 
| 31 | 
            +
                        if process.command != command
         | 
| 32 | 
            +
                          process.command = command
         | 
| 33 | 
            +
                          Procodile.log nil, 'system', "#{name} command has changed. Updated."
         | 
| 34 | 
            +
                        end
         | 
| 35 | 
            +
                        process.options = options_for_process(name)
         | 
| 35 36 | 
             
                      else
         | 
| 36 | 
            -
                         | 
| 37 | 
            +
                        Procodile.log nil, 'system', "#{name} has been added to the Procfile. Adding it."
         | 
| 38 | 
            +
                        @processes[name] = create_process(name, command, COLORS[@processes.size.divmod(COLORS.size)[1]])
         | 
| 37 39 | 
             
                      end
         | 
| 38 | 
            -
                    else
         | 
| 39 | 
            -
                      Procodile.log nil, 'system', "#{name} has been added to the Procfile. Adding it."
         | 
| 40 | 
            -
                      @processes[name] = Process.new(self, name, command, process_options[name] || {})
         | 
| 41 | 
            -
                      @processes[name].log_color = COLORS[@processes.size.divmod(COLORS.size)[1]]
         | 
| 42 40 | 
             
                    end
         | 
| 43 41 | 
             
                  end
         | 
| 44 | 
            -
             | 
| 45 42 | 
             
                end
         | 
| 46 43 |  | 
| 47 44 | 
             
                def app_name
         | 
| 48 | 
            -
                  @app_name ||= options['app_name'] || 'Procodile'
         | 
| 45 | 
            +
                  @app_name ||= local_options['app_name'] || options['app_name'] || 'Procodile'
         | 
| 49 46 | 
             
                end
         | 
| 50 47 |  | 
| 51 48 | 
             
                def processes
         | 
| 52 49 | 
             
                  @processes ||= process_list.each_with_index.each_with_object({}) do |((name, command), index), hash|
         | 
| 53 | 
            -
                    hash[name] =  | 
| 54 | 
            -
                    hash[name].log_color = COLORS[index.divmod(COLORS.size)[1]]
         | 
| 50 | 
            +
                    hash[name] = create_process(name, command, COLORS[index.divmod(COLORS.size)[1]])
         | 
| 55 51 | 
             
                  end
         | 
| 56 52 | 
             
                end
         | 
| 57 53 |  | 
| @@ -67,12 +63,32 @@ module Procodile | |
| 67 63 | 
             
                  @process_options ||= options['processes'] || {}
         | 
| 68 64 | 
             
                end
         | 
| 69 65 |  | 
| 66 | 
            +
                def local_options
         | 
| 67 | 
            +
                  @local_options ||= File.exist?(local_options_path) ? YAML.load_file(local_options_path) : {}
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                def local_process_options
         | 
| 71 | 
            +
                  @local_process_options ||= local_options['processes'] || {}
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def options_for_process(name)
         | 
| 75 | 
            +
                  (process_options[name] || {}).merge(local_process_options[name] || {})
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                def environment_variables
         | 
| 79 | 
            +
                  (options['env'] || {}).merge(local_options['env'] || {})
         | 
| 80 | 
            +
                end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                def local_environment_variables
         | 
| 83 | 
            +
                  @local_environment_variables ||= local_options['env'] || {}
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 70 86 | 
             
                def pid_root
         | 
| 71 | 
            -
                  @pid_root ||= File.expand_path(options['pid_root'] || 'pids', @root)
         | 
| 87 | 
            +
                  @pid_root ||= File.expand_path(local_options['pid_root'] || options['pid_root'] || 'pids', @root)
         | 
| 72 88 | 
             
                end
         | 
| 73 89 |  | 
| 74 90 | 
             
                def log_path
         | 
| 75 | 
            -
                  @log_path ||= File.expand_path(options['log_path'] || 'procodile.log', @root)
         | 
| 91 | 
            +
                  @log_path ||= File.expand_path(local_options['log_path'] || options['log_path'] || 'procodile.log', @root)
         | 
| 76 92 | 
             
                end
         | 
| 77 93 |  | 
| 78 94 | 
             
                def sock_path
         | 
| @@ -89,5 +105,15 @@ module Procodile | |
| 89 105 | 
             
                  File.join(@root, 'Procfile.options')
         | 
| 90 106 | 
             
                end
         | 
| 91 107 |  | 
| 108 | 
            +
                def local_options_path
         | 
| 109 | 
            +
                  File.join(@root, 'Procfile.local')
         | 
| 110 | 
            +
                end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                def create_process(name, command, log_color)
         | 
| 113 | 
            +
                  process = Process.new(self, name, command, options_for_process(name))
         | 
| 114 | 
            +
                  process.log_color = log_color
         | 
| 115 | 
            +
                  process
         | 
| 116 | 
            +
                end
         | 
| 117 | 
            +
             | 
| 92 118 | 
             
              end
         | 
| 93 119 | 
             
            end
         | 
| @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            require 'json'
         | 
| 2 | 
            +
            require 'procodile/version'
         | 
| 2 3 |  | 
| 3 4 | 
             
            module Procodile
         | 
| 4 5 | 
             
              class ControlSession
         | 
| @@ -30,7 +31,7 @@ module Procodile | |
| 30 31 | 
             
                end
         | 
| 31 32 |  | 
| 32 33 | 
             
                def stop(options)
         | 
| 33 | 
            -
                  instances = @supervisor.stop(:processes => options['processes'])
         | 
| 34 | 
            +
                  instances = @supervisor.stop(:processes => options['processes'], :stop_supervisor => options['stop_supervisor'])
         | 
| 34 35 | 
             
                  "200 " + instances.map(&:to_hash).to_json
         | 
| 35 36 | 
             
                end
         | 
| 36 37 |  | 
| @@ -44,23 +45,32 @@ module Procodile | |
| 44 45 | 
             
                  "200"
         | 
| 45 46 | 
             
                end
         | 
| 46 47 |  | 
| 48 | 
            +
                def check_concurrency(options)
         | 
| 49 | 
            +
                  result = @supervisor.check_concurrency(:reload => options['reload'])
         | 
| 50 | 
            +
                  result = result.each_with_object({}) { |(type, instances), hash| hash[type] = instances.map(&:to_hash) }
         | 
| 51 | 
            +
                  "200 #{result.to_json}"
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
             | 
| 47 55 | 
             
                def status(options)
         | 
| 48 56 | 
             
                  instances = {}
         | 
| 49 57 | 
             
                  @supervisor.processes.each do |process, process_instances|
         | 
| 50 58 | 
             
                    instances[process.name] = []
         | 
| 51 59 | 
             
                    for instance in process_instances
         | 
| 52 | 
            -
                      instances[process.name] <<  | 
| 53 | 
            -
                        :description => instance.description,
         | 
| 54 | 
            -
                        :pid => instance.pid,
         | 
| 55 | 
            -
                        :running => instance.running?,
         | 
| 56 | 
            -
                        :respawns => instance.respawns,
         | 
| 57 | 
            -
                        :command => instance.process.command
         | 
| 58 | 
            -
                      }
         | 
| 60 | 
            +
                      instances[process.name] << instance.to_hash
         | 
| 59 61 | 
             
                    end
         | 
| 60 62 | 
             
                  end
         | 
| 61 63 |  | 
| 62 64 | 
             
                  processes = @supervisor.processes.keys.map(&:to_hash)
         | 
| 63 | 
            -
                  result = { | 
| 65 | 
            +
                  result = {
         | 
| 66 | 
            +
                    :version => Procodile::VERSION,
         | 
| 67 | 
            +
                    :root => @supervisor.config.root,
         | 
| 68 | 
            +
                    :app_name => @supervisor.config.app_name,
         | 
| 69 | 
            +
                    :supervisor => @supervisor.to_hash,
         | 
| 70 | 
            +
                    :instances => instances,
         | 
| 71 | 
            +
                    :processes => processes,
         | 
| 72 | 
            +
                    :environment_variables => @supervisor.config.environment_variables
         | 
| 73 | 
            +
                  }
         | 
| 64 74 | 
             
                  "200 #{result.to_json}"
         | 
| 65 75 | 
             
                end
         | 
| 66 76 |  | 
    
        data/lib/procodile/instance.rb
    CHANGED
    
    | @@ -13,6 +13,7 @@ module Procodile | |
| 13 13 | 
             
                  @id = id
         | 
| 14 14 | 
             
                  @respawns = 0
         | 
| 15 15 | 
             
                  @respawnable = true
         | 
| 16 | 
            +
                  @started_at = nil
         | 
| 16 17 | 
             
                end
         | 
| 17 18 |  | 
| 18 19 | 
             
                #
         | 
| @@ -22,6 +23,16 @@ module Procodile | |
| 22 23 | 
             
                  "#{@process.name}.#{@id}"
         | 
| 23 24 | 
             
                end
         | 
| 24 25 |  | 
| 26 | 
            +
                #
         | 
| 27 | 
            +
                # Return an array of environment variables that should be set
         | 
| 28 | 
            +
                #
         | 
| 29 | 
            +
                def environment_variables
         | 
| 30 | 
            +
                  @process.config.environment_variables.merge({
         | 
| 31 | 
            +
                    'PID_FILE' => self.pid_file_path,
         | 
| 32 | 
            +
                    'APP_ROOT' => @process.config.root
         | 
| 33 | 
            +
                  })
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 25 36 | 
             
                #
         | 
| 26 37 | 
             
                # Should this instance still be monitored by the supervisor?
         | 
| 27 38 | 
             
                #
         | 
| @@ -64,7 +75,7 @@ module Procodile | |
| 64 75 | 
             
                #
         | 
| 65 76 | 
             
                # Start a new instance of this process
         | 
| 66 77 | 
             
                #
         | 
| 67 | 
            -
                def start
         | 
| 78 | 
            +
                def start(&block)
         | 
| 68 79 | 
             
                  @stopping = false
         | 
| 69 80 | 
             
                  existing_pid = self.pid_from_file
         | 
| 70 81 | 
             
                  if running?(existing_pid)
         | 
| @@ -72,6 +83,7 @@ module Procodile | |
| 72 83 | 
             
                    # to monitor this process rather than spawning a new one.
         | 
| 73 84 | 
             
                    @pid = existing_pid
         | 
| 74 85 | 
             
                    Procodile.log(@process.log_color, description, "Already running with PID #{@pid}")
         | 
| 86 | 
            +
                    @started_at = File.mtime(self.pid_file_path)
         | 
| 75 87 | 
             
                    nil
         | 
| 76 88 | 
             
                  else
         | 
| 77 89 | 
             
                    if self.process.log_path
         | 
| @@ -84,10 +96,15 @@ module Procodile | |
| 84 96 | 
             
                    end
         | 
| 85 97 |  | 
| 86 98 | 
             
                    Dir.chdir(@process.config.root)
         | 
| 87 | 
            -
                    @pid = ::Process.spawn( | 
| 99 | 
            +
                    @pid = ::Process.spawn(environment_variables, @process.command, :out => log_destination, :err => log_destination, :pgroup => true)
         | 
| 88 100 | 
             
                    Procodile.log(@process.log_color, description, "Started with PID #{@pid}")
         | 
| 89 101 | 
             
                    File.open(pid_file_path, 'w') { |f| f.write(@pid.to_s + "\n") }
         | 
| 90 102 | 
             
                    ::Process.detach(@pid)
         | 
| 103 | 
            +
                    @started_at = Time.now
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    if block_given?
         | 
| 106 | 
            +
                      block.call(self, return_value)
         | 
| 107 | 
            +
                    end
         | 
| 91 108 |  | 
| 92 109 | 
             
                    return_value
         | 
| 93 110 | 
             
                  end
         | 
| @@ -120,6 +137,7 @@ module Procodile | |
| 120 137 | 
             
                # started again
         | 
| 121 138 | 
             
                #
         | 
| 122 139 | 
             
                def on_stop
         | 
| 140 | 
            +
                  @started_at = nil
         | 
| 123 141 | 
             
                  tidy
         | 
| 124 142 | 
             
                  unmonitor
         | 
| 125 143 | 
             
                end
         | 
| @@ -135,7 +153,7 @@ module Procodile | |
| 135 153 | 
             
                #
         | 
| 136 154 | 
             
                # Retarts the process using the appropriate method from the process configuraiton
         | 
| 137 155 | 
             
                #
         | 
| 138 | 
            -
                def restart
         | 
| 156 | 
            +
                def restart(&block)
         | 
| 139 157 | 
             
                  Procodile.log(@process.log_color, description, "Restarting using #{@process.restart_mode} mode")
         | 
| 140 158 | 
             
                  @restarting = true
         | 
| 141 159 | 
             
                  update_pid
         | 
| @@ -146,11 +164,11 @@ module Procodile | |
| 146 164 | 
             
                      Procodile.log(@process.log_color, description, "Sent #{@process.restart_mode.upcase} signal to process #{@pid}")
         | 
| 147 165 | 
             
                    else
         | 
| 148 166 | 
             
                      Procodile.log(@process.log_color, description, "Process not running already. Starting it.")
         | 
| 149 | 
            -
                      start
         | 
| 167 | 
            +
                      start(&block)
         | 
| 150 168 | 
             
                    end
         | 
| 151 169 | 
             
                  when 'start-term'
         | 
| 152 170 | 
             
                    old_process_pid = @pid
         | 
| 153 | 
            -
                    start
         | 
| 171 | 
            +
                    start(&block)
         | 
| 154 172 | 
             
                    Procodile.log(@process.log_color, description, "Sent #{@process.term_signal} signal to old PID #{old_process_pid} (forgetting now)")
         | 
| 155 173 | 
             
                    ::Process.kill(@process.term_signal, old_process_pid)
         | 
| 156 174 | 
             
                  when 'term-start'
         | 
| @@ -158,7 +176,7 @@ module Procodile | |
| 158 176 | 
             
                    Thread.new do
         | 
| 159 177 | 
             
                      # Wait for this process to stop and when it has, run it.
         | 
| 160 178 | 
             
                      sleep 0.5 while running?
         | 
| 161 | 
            -
                      start
         | 
| 179 | 
            +
                      start(&block)
         | 
| 162 180 | 
             
                    end
         | 
| 163 181 | 
             
                  end
         | 
| 164 182 | 
             
                ensure
         | 
| @@ -259,7 +277,8 @@ module Procodile | |
| 259 277 | 
             
                    :description => self.description,
         | 
| 260 278 | 
             
                    :pid => self.pid,
         | 
| 261 279 | 
             
                    :respawns => self.respawns,
         | 
| 262 | 
            -
                    :running => self.running | 
| 280 | 
            +
                    :running => self.running?,
         | 
| 281 | 
            +
                    :started_at => @started_at ? @started_at.to_i : nil
         | 
| 263 282 | 
             
                  }
         | 
| 264 283 | 
             
                end
         | 
| 265 284 |  | 
| @@ -0,0 +1,77 @@ | |
| 1 | 
            +
            module Procodile
         | 
| 2 | 
            +
              class StatusCLIOutput
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def initialize(status)
         | 
| 5 | 
            +
                  @status = status
         | 
| 6 | 
            +
                end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                def print_all
         | 
| 9 | 
            +
                  print_header
         | 
| 10 | 
            +
                  print_processes
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def print_header
         | 
| 14 | 
            +
                  puts "Procodile Version   " + @status['version'].to_s.color(34)
         | 
| 15 | 
            +
                  puts "Application Root    " + "#{@status['root']}".color(34)
         | 
| 16 | 
            +
                  puts "Supervisor PID      " + "#{@status['supervisor']['pid']}".color(34)
         | 
| 17 | 
            +
                  if time = @status['supervisor']['started_at']
         | 
| 18 | 
            +
                    time = Time.at(time)
         | 
| 19 | 
            +
                    puts "Started             " + "#{time.to_s}".color(34)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                  if @status['environment_variables'] && !@status['environment_variables'].empty?
         | 
| 22 | 
            +
                    @status['environment_variables'].each_with_index do |(key, value), index|
         | 
| 23 | 
            +
                      if index == 0
         | 
| 24 | 
            +
                        print "Environment Vars    "
         | 
| 25 | 
            +
                      else
         | 
| 26 | 
            +
                        print "                    "
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
                      print key.color(34)
         | 
| 29 | 
            +
                      puts " " + value.to_s
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def print_processes
         | 
| 35 | 
            +
                  puts
         | 
| 36 | 
            +
                  @status['processes'].each_with_index do |process, index|
         | 
| 37 | 
            +
                    puts unless index == 0
         | 
| 38 | 
            +
                    puts "|| ".color(process['log_color']) + process['name'].color(process['log_color'])
         | 
| 39 | 
            +
                    puts "||".color(process['log_color']) + " Quantity            " + process['quantity'].to_s
         | 
| 40 | 
            +
                    puts "||".color(process['log_color']) + " Command             " + process['command']
         | 
| 41 | 
            +
                    puts "||".color(process['log_color']) + " Respawning          " + "#{process['max_respawns']} every #{process['respawn_window']} seconds"
         | 
| 42 | 
            +
                    puts "||".color(process['log_color']) + " Restart mode        " + process['restart_mode']
         | 
| 43 | 
            +
                    puts "||".color(process['log_color']) + " Log path            " + (process['log_path'] || "none specified")
         | 
| 44 | 
            +
                    instances = @status['instances'][process['name']]
         | 
| 45 | 
            +
                    if instances.empty?
         | 
| 46 | 
            +
                      puts "||".color(process['log_color']) + " No processes running."
         | 
| 47 | 
            +
                    else
         | 
| 48 | 
            +
                      instances.each do |instance|
         | 
| 49 | 
            +
                        print "|| => ".color(process['log_color']) + instance['description'].to_s.ljust(17, ' ').color(process['log_color'])
         | 
| 50 | 
            +
                        if instance['running']
         | 
| 51 | 
            +
                          print 'Running'.color("32")
         | 
| 52 | 
            +
                        else
         | 
| 53 | 
            +
                          print 'Stopped'.color("31")
         | 
| 54 | 
            +
                        end
         | 
| 55 | 
            +
                        print "   " + formatted_timestamp(instance['started_at']).ljust(10, ' ')
         | 
| 56 | 
            +
                        print "   pid: " + instance['pid'].to_s.ljust(7, ' ')
         | 
| 57 | 
            +
                        print "   respawns: " + instance['respawns'].to_s.ljust(7, ' ')
         | 
| 58 | 
            +
                        puts
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                private
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def formatted_timestamp(timestamp)
         | 
| 67 | 
            +
                  return '' if timestamp.nil?
         | 
| 68 | 
            +
                  timestamp = Time.at(timestamp)
         | 
| 69 | 
            +
                  if timestamp > (Time.now - (60 * 60 * 24))
         | 
| 70 | 
            +
                    timestamp.strftime("%H:%M")
         | 
| 71 | 
            +
                  else
         | 
| 72 | 
            +
                    timestamp.strftime("%d/%m/%Y")
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
              end
         | 
| 77 | 
            +
            end
         | 
    
        data/lib/procodile/supervisor.rb
    CHANGED
    
    | @@ -5,6 +5,7 @@ module Procodile | |
| 5 5 |  | 
| 6 6 | 
             
                attr_reader :config
         | 
| 7 7 | 
             
                attr_reader :processes
         | 
| 8 | 
            +
                attr_reader :started_at
         | 
| 8 9 |  | 
| 9 10 | 
             
                def initialize(config, run_options = {})
         | 
| 10 11 | 
             
                  @config = config
         | 
| @@ -13,7 +14,7 @@ module Procodile | |
| 13 14 | 
             
                  @readers = {}
         | 
| 14 15 | 
             
                  @signal_handler = SignalHandler.new('TERM', 'USR1', 'USR2', 'INT', 'HUP')
         | 
| 15 16 | 
             
                  @signal_handler.register('TERM') { stop_supervisor }
         | 
| 16 | 
            -
                  @signal_handler.register('INT') { stop }
         | 
| 17 | 
            +
                  @signal_handler.register('INT') { stop(:stop_supervisor => true) }
         | 
| 17 18 | 
             
                  @signal_handler.register('USR1') { restart }
         | 
| 18 19 | 
             
                  @signal_handler.register('USR2') { status }
         | 
| 19 20 | 
             
                  @signal_handler.register('HUP') { reload_config }
         | 
| @@ -30,10 +31,12 @@ module Procodile | |
| 30 31 | 
             
                  end
         | 
| 31 32 | 
             
                  start_processes(options[:processes])
         | 
| 32 33 | 
             
                  watch_for_output
         | 
| 34 | 
            +
                  @started_at = Time.now
         | 
| 33 35 | 
             
                  loop { supervise; sleep 3 }
         | 
| 34 36 | 
             
                end
         | 
| 35 37 |  | 
| 36 38 | 
             
                def start_processes(types = [])
         | 
| 39 | 
            +
                  reload_config
         | 
| 37 40 | 
             
                  Array.new.tap do |instances_started|
         | 
| 38 41 | 
             
                    @config.processes.each do |name, process|
         | 
| 39 42 | 
             
                      next if types && !types.include?(name.to_s) # Not a process we want
         | 
| @@ -45,17 +48,19 @@ module Procodile | |
| 45 48 | 
             
                end
         | 
| 46 49 |  | 
| 47 50 | 
             
                def stop(options = {})
         | 
| 51 | 
            +
                  if options[:stop_supervisor]
         | 
| 52 | 
            +
                    @run_options[:stop_when_none] = true
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
             | 
| 48 55 | 
             
                  Array.new.tap do |instances_stopped|
         | 
| 49 56 | 
             
                    if options[:processes].nil?
         | 
| 50 | 
            -
                      return if @stopping
         | 
| 51 | 
            -
                      @stopping = true
         | 
| 52 57 | 
             
                      Procodile.log nil, "system", "Stopping all #{@config.app_name} processes"
         | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
                          end
         | 
| 58 | 
            +
                      @processes.each do |_, instances|
         | 
| 59 | 
            +
                        instances.each do |instance|
         | 
| 60 | 
            +
                          instance.stop
         | 
| 61 | 
            +
                          instances_stopped << instance
         | 
| 58 62 | 
             
                        end
         | 
| 63 | 
            +
                      end
         | 
| 59 64 | 
             
                    else
         | 
| 60 65 | 
             
                      instances = process_names_to_instances(options[:processes])
         | 
| 61 66 | 
             
                      Procodile.log nil, "system", "Stopping #{instances.size} process(es)"
         | 
| @@ -68,23 +73,25 @@ module Procodile | |
| 68 73 | 
             
                end
         | 
| 69 74 |  | 
| 70 75 | 
             
                def restart(options = {})
         | 
| 71 | 
            -
                   | 
| 76 | 
            +
                  reload_config
         | 
| 72 77 | 
             
                  Array.new.tap do |instances_restarted|
         | 
| 73 78 | 
             
                    if options[:processes].nil?
         | 
| 74 79 | 
             
                      Procodile.log nil, "system", "Restarting all #{@config.app_name} processes"
         | 
| 75 80 | 
             
                      @processes.each do |_, instances|
         | 
| 76 81 | 
             
                        instances.each do |instance|
         | 
| 77 | 
            -
                          instance.restart
         | 
| 82 | 
            +
                          instance.restart { |_, io| add_reader(instance, io) }
         | 
| 78 83 | 
             
                          instances_restarted << instance
         | 
| 79 84 | 
             
                        end
         | 
| 80 85 | 
             
                      end
         | 
| 86 | 
            +
                      instances_restarted.push(*check_instance_quantities[:started])
         | 
| 81 87 | 
             
                    else
         | 
| 82 88 | 
             
                      instances = process_names_to_instances(options[:processes])
         | 
| 83 89 | 
             
                      Procodile.log nil, "system", "Restarting #{instances.size} process(es)"
         | 
| 84 90 | 
             
                      instances.each do |instance|
         | 
| 85 | 
            -
                        instance.restart
         | 
| 91 | 
            +
                        instance.restart { |_, io| add_reader(instance, io) }
         | 
| 86 92 | 
             
                        instances_restarted << instance
         | 
| 87 93 | 
             
                      end
         | 
| 94 | 
            +
                      instances_restarted.push(*check_instance_quantities(options[:processes])[:started])
         | 
| 88 95 | 
             
                    end
         | 
| 89 96 | 
             
                  end
         | 
| 90 97 | 
             
                end
         | 
| @@ -116,29 +123,66 @@ module Procodile | |
| 116 123 | 
             
                    end
         | 
| 117 124 | 
             
                  end
         | 
| 118 125 |  | 
| 119 | 
            -
                   | 
| 120 | 
            -
             | 
| 121 | 
            -
                     | 
| 122 | 
            -
             | 
| 126 | 
            +
                  if @run_options[:stop_when_none]
         | 
| 127 | 
            +
                    # If the processes go away, we can stop the supervisor now
         | 
| 128 | 
            +
                    if @processes.all? { |_,instances| instances.size == 0 }
         | 
| 129 | 
            +
                      Procodile.log nil, "system", "All processes have stopped"
         | 
| 130 | 
            +
                      stop_supervisor
         | 
| 131 | 
            +
                    end
         | 
| 123 132 | 
             
                  end
         | 
| 124 133 | 
             
                end
         | 
| 125 134 |  | 
| 126 135 | 
             
                def reload_config
         | 
| 127 136 | 
             
                  Procodile.log nil, "system", "Reloading configuration"
         | 
| 128 137 | 
             
                  @config.reload
         | 
| 129 | 
            -
             | 
| 138 | 
            +
                end
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                def check_concurrency(options = {})
         | 
| 141 | 
            +
                  Procodile.log nil, "system", "Checking process concurrency"
         | 
| 142 | 
            +
                  reload_config unless options[:reload] == false
         | 
| 143 | 
            +
                  result = check_instance_quantities
         | 
| 144 | 
            +
                  if result[:started].empty? && result[:stopped].empty?
         | 
| 145 | 
            +
                    Procodile.log nil, "system", "Process concurrency looks good"
         | 
| 146 | 
            +
                  else
         | 
| 147 | 
            +
                    unless result[:started].empty?
         | 
| 148 | 
            +
                      Procodile.log nil, "system", "Concurrency check started #{result[:started].map(&:description).join(', ')}"
         | 
| 149 | 
            +
                    end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    unless result[:stopped].empty?
         | 
| 152 | 
            +
                      Procodile.log nil, "system", "Concurrency check stopped #{result[:stopped].map(&:description).join(', ')}"
         | 
| 153 | 
            +
                    end
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
                  result
         | 
| 156 | 
            +
                end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                def to_hash
         | 
| 159 | 
            +
                  {
         | 
| 160 | 
            +
                    :started_at => @started_at ? @started_at.to_i : nil,
         | 
| 161 | 
            +
                    :pid => ::Process.pid
         | 
| 162 | 
            +
                  }
         | 
| 130 163 | 
             
                end
         | 
| 131 164 |  | 
| 132 165 | 
             
                private
         | 
| 133 166 |  | 
| 167 | 
            +
                def add_reader(instance, io)
         | 
| 168 | 
            +
                  return unless io
         | 
| 169 | 
            +
                  @readers[io] = instance
         | 
| 170 | 
            +
                  @signal_handler.notice
         | 
| 171 | 
            +
                end
         | 
| 172 | 
            +
             | 
| 134 173 | 
             
                def watch_for_output
         | 
| 135 174 | 
             
                  Thread.new do
         | 
| 136 175 | 
             
                    loop do
         | 
| 137 176 | 
             
                      io = IO.select([@signal_handler.pipe[:reader]] + @readers.keys, nil, nil, 30)
         | 
| 138 177 | 
             
                      @signal_handler.handle
         | 
| 178 | 
            +
             | 
| 139 179 | 
             
                      if io
         | 
| 140 180 | 
             
                        io.first.each do |reader|
         | 
| 141 | 
            -
                           | 
| 181 | 
            +
                          if reader == @signal_handler.pipe[:reader]
         | 
| 182 | 
            +
                            @signal_handler.pipe[:reader].read_nonblock(999) rescue nil
         | 
| 183 | 
            +
                            next
         | 
| 184 | 
            +
                          end
         | 
| 185 | 
            +
             | 
| 142 186 | 
             
                          if reader.eof?
         | 
| 143 187 | 
             
                            @readers.delete(reader)
         | 
| 144 188 | 
             
                          else
         | 
| @@ -155,17 +199,20 @@ module Procodile | |
| 155 199 | 
             
                  end
         | 
| 156 200 | 
             
                end
         | 
| 157 201 |  | 
| 158 | 
            -
                def check_instance_quantities
         | 
| 159 | 
            -
                   | 
| 160 | 
            -
                     | 
| 161 | 
            -
                       | 
| 162 | 
            -
                       | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
             | 
| 166 | 
            -
                       | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 202 | 
            +
                def check_instance_quantities(processes = nil)
         | 
| 203 | 
            +
                  {:started => [], :stopped => []}.tap do |status|
         | 
| 204 | 
            +
                    @processes.each do |process, instances|
         | 
| 205 | 
            +
                      next if processes && !processes.include?(process.name)
         | 
| 206 | 
            +
                      if instances.size > process.quantity
         | 
| 207 | 
            +
                        quantity_to_stop = instances.size - process.quantity
         | 
| 208 | 
            +
                        Procodile.log nil, "system", "Stopping #{quantity_to_stop} #{process.name} process(es)"
         | 
| 209 | 
            +
                        status[:stopped] = instances.last(quantity_to_stop).each(&:stop)
         | 
| 210 | 
            +
                      elsif instances.size < process.quantity
         | 
| 211 | 
            +
                        quantity_needed = process.quantity - instances.size
         | 
| 212 | 
            +
                        start_id = instances.last ? instances.last.id + 1 : 1
         | 
| 213 | 
            +
                        Procodile.log nil, "system", "Starting #{quantity_needed} more #{process.name} process(es) (start with #{start_id})"
         | 
| 214 | 
            +
                        status[:started] = start_instances(process.generate_instances(quantity_needed, start_id))
         | 
| 215 | 
            +
                      end
         | 
| 169 216 | 
             
                    end
         | 
| 170 217 | 
             
                  end
         | 
| 171 218 | 
             
                end
         | 
| @@ -175,8 +222,7 @@ module Procodile | |
| 175 222 | 
             
                    if @run_options[:brittle]
         | 
| 176 223 | 
             
                      instance.respawnable = false
         | 
| 177 224 | 
             
                    end
         | 
| 178 | 
            -
                    io  | 
| 179 | 
            -
                    @readers[io] = instance if io
         | 
| 225 | 
            +
                    instance.start { |_, io| add_reader(instance, io) }
         | 
| 180 226 | 
             
                    @processes[instance.process] ||= []
         | 
| 181 227 | 
             
                    @processes[instance.process] << instance
         | 
| 182 228 | 
             
                  end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: procodile
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1.0. | 
| 4 | 
            +
              version: 1.0.3
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Adam Cooke
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2016-10- | 
| 11 | 
            +
            date: 2016-10-18 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: json
         | 
| @@ -34,8 +34,6 @@ extra_rdoc_files: [] | |
| 34 34 | 
             
            files:
         | 
| 35 35 | 
             
            - bin/procodile
         | 
| 36 36 | 
             
            - lib/procodile.rb
         | 
| 37 | 
            -
            - lib/procodile/capistrano2.rb
         | 
| 38 | 
            -
            - lib/procodile/capistrano3.rb
         | 
| 39 37 | 
             
            - lib/procodile/cli.rb
         | 
| 40 38 | 
             
            - lib/procodile/config.rb
         | 
| 41 39 | 
             
            - lib/procodile/control_client.rb
         | 
| @@ -46,7 +44,9 @@ files: | |
| 46 44 | 
             
            - lib/procodile/logger.rb
         | 
| 47 45 | 
             
            - lib/procodile/process.rb
         | 
| 48 46 | 
             
            - lib/procodile/signal_handler.rb
         | 
| 47 | 
            +
            - lib/procodile/status_cli_output.rb
         | 
| 49 48 | 
             
            - lib/procodile/supervisor.rb
         | 
| 49 | 
            +
            - lib/procodile/version.rb
         | 
| 50 50 | 
             
            homepage: https://github.com/adamcooke/procodile
         | 
| 51 51 | 
             
            licenses:
         | 
| 52 52 | 
             
            - MIT
         | 
| @@ -1,39 +0,0 @@ | |
| 1 | 
            -
            Capistrano::Configuration.instance(:must_exist).load do
         | 
| 2 | 
            -
             | 
| 3 | 
            -
              namespace :procodile do
         | 
| 4 | 
            -
             | 
| 5 | 
            -
                task :start, :roles => fetch(:procodile_roles, [:app]) do
         | 
| 6 | 
            -
                  run procodile_command('start')
         | 
| 7 | 
            -
                end
         | 
| 8 | 
            -
             | 
| 9 | 
            -
                task :stop, :roles => fetch(:procodile_roles, [:app]) do
         | 
| 10 | 
            -
                  run procodile_command('stop')
         | 
| 11 | 
            -
                end
         | 
| 12 | 
            -
             | 
| 13 | 
            -
                task :restart, :roles => fetch(:procodile_roles, [:app]) do
         | 
| 14 | 
            -
                  run procodile_command('restart')
         | 
| 15 | 
            -
                end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                task :status, :roles => fetch(:procodile_roles, [:app]) do
         | 
| 18 | 
            -
                  run procodile_command('status')
         | 
| 19 | 
            -
                end
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                after 'deploy:start', 'procodile:start'
         | 
| 22 | 
            -
                after 'deploy:stop', 'procodile:stop'
         | 
| 23 | 
            -
                after 'deploy:restart', 'procodile:restart'
         | 
| 24 | 
            -
             | 
| 25 | 
            -
                def procodile_command(command, options = "")
         | 
| 26 | 
            -
                  binary = fetch(:procodile_binary, 'procodile')
         | 
| 27 | 
            -
                  if processes = fetch(:processes, nil)
         | 
| 28 | 
            -
                    options = "-p #{processes} " + options
         | 
| 29 | 
            -
                  end
         | 
| 30 | 
            -
                  command = "#{binary} #{command} -r #{current_path} #{options}"
         | 
| 31 | 
            -
                  if user = fetch(:procodile_user, nil)
         | 
| 32 | 
            -
                    "sudo -u #{user} #{command}"
         | 
| 33 | 
            -
                  else
         | 
| 34 | 
            -
                    command
         | 
| 35 | 
            -
                  end
         | 
| 36 | 
            -
                end
         | 
| 37 | 
            -
             | 
| 38 | 
            -
              end
         | 
| 39 | 
            -
            end
         | 
| @@ -1,40 +0,0 @@ | |
| 1 | 
            -
            namespace :procodile do
         | 
| 2 | 
            -
              desc 'Start procodile processes'
         | 
| 3 | 
            -
              task :start do
         | 
| 4 | 
            -
                on roles(fetch(:procodile_roles, [:app])) do
         | 
| 5 | 
            -
                  execute procodile_command(:start)
         | 
| 6 | 
            -
                end
         | 
| 7 | 
            -
              end
         | 
| 8 | 
            -
             | 
| 9 | 
            -
              desc 'Stop procodile processes'
         | 
| 10 | 
            -
              task :stop do
         | 
| 11 | 
            -
                on roles(fetch(:procodile_roles, [:app])) do
         | 
| 12 | 
            -
                  execute procodile_command(:stop)
         | 
| 13 | 
            -
                end
         | 
| 14 | 
            -
              end
         | 
| 15 | 
            -
             | 
| 16 | 
            -
              desc 'Restart procodile processes'
         | 
| 17 | 
            -
              task :restart do
         | 
| 18 | 
            -
                on roles(fetch(:procodile_roles, [:app])) do
         | 
| 19 | 
            -
                  execute procodile_command(:restart)
         | 
| 20 | 
            -
                end
         | 
| 21 | 
            -
              end
         | 
| 22 | 
            -
             | 
| 23 | 
            -
              after 'deploy:start', "procodile:start"
         | 
| 24 | 
            -
              after 'deploy:stop', "procodile:stop"
         | 
| 25 | 
            -
              after 'deploy:restart', "procodile:restart"
         | 
| 26 | 
            -
             | 
| 27 | 
            -
              def procodile_command(command, options = "")
         | 
| 28 | 
            -
                binary = fetch(:procodile_binary, 'procodile')
         | 
| 29 | 
            -
                if processes = fetch(:processes, nil)
         | 
| 30 | 
            -
                  options = "-p #{processes} " + options
         | 
| 31 | 
            -
                end
         | 
| 32 | 
            -
                command = "#{binary} #{command} -r #{current_path} #{options}"
         | 
| 33 | 
            -
                if user = fetch(:procodile_user, nil)
         | 
| 34 | 
            -
                  "sudo -u #{user} #{command}"
         | 
| 35 | 
            -
                else
         | 
| 36 | 
            -
                  command
         | 
| 37 | 
            -
                end
         | 
| 38 | 
            -
              end
         | 
| 39 | 
            -
             | 
| 40 | 
            -
            end
         |