class Procodile::CLI

Attributes

config[RW]
options[RW]

Public Class Methods

command(name) click to toggle source
# File lib/procodile/cli.rb, line 25
def self.command(name)
  commands[name] = {:name => name, :description => @description, :options => @options}
  @description = nil
  @options = nil
end
commands() click to toggle source
# File lib/procodile/cli.rb, line 13
def self.commands
  @commands ||= {}
end
desc(description) click to toggle source
# File lib/procodile/cli.rb, line 17
def self.desc(description)
  @description = description
end
new() click to toggle source
# File lib/procodile/cli.rb, line 34
def initialize
  @options = {}
end
options(&block) click to toggle source
# File lib/procodile/cli.rb, line 21
def self.options(&block)
  @options = block
end

Private Class Methods

pid_active?(pid) click to toggle source
# File lib/procodile/cli.rb, line 472
def self.pid_active?(pid)
  ::Process.getpgid(pid) ? true : false
rescue Errno::ESRCH
  false
end
start_supervisor(config, options = {}, &after_start) click to toggle source
# File lib/procodile/cli.rb, line 496
def self.start_supervisor(config, options = {}, &after_start)
  run_options = {}
  run_options[:respawn] = options[:respawn]
  run_options[:stop_when_none] = options[:stop_when_none]
  run_options[:proxy] = options[:proxy]
  run_options[:force_single_log] = options[:foreground]
  run_options[:port_allocations] = options[:port_allocations]

  tidy_pids(config)

  if options[:clean]
    FileUtils.rm_rf(Dir[File.join(config.pid_root, '*')])
    puts "Emptied PID directory"
  end

  if !Dir[File.join(config.pid_root, "*")].empty?
    raise Error, "The PID directory (#{config.pid_root}) is not empty. Cannot start unless things are clean."
  end

  $0="[procodile] #{config.app_name} (#{config.root})"
  if options[:foreground]
    File.open(config.supervisor_pid_path, 'w') { |f| f.write(::Process.pid) }
    Supervisor.new(config, run_options).start(&after_start)
  else
    FileUtils.rm_f(File.join(config.pid_root, "*.pid"))
    pid = fork do
      STDOUT.reopen(config.log_path, 'a')
      STDOUT.sync = true
      STDERR.reopen(config.log_path, 'a')
      STDERR.sync = true
      Supervisor.new(config, run_options).start(&after_start)
    end
    ::Process.detach(pid)
    File.open(config.supervisor_pid_path, 'w') { |f| f.write(pid) }
    puts "Started Procodile supervisor with PID #{pid}"
  end
end
tidy_pids(config) click to toggle source
# File lib/procodile/cli.rb, line 534
def self.tidy_pids(config)
  FileUtils.rm_f(config.supervisor_pid_path)
  FileUtils.rm_f(config.sock_path)
  pid_files = Dir[File.join(config.pid_root, '*.pid')]
  pid_files.each do |pid_path|
    file_name = pid_path.split('/').last
    pid = File.read(pid_path).to_i
    if self.pid_active?(pid)
      puts "Could not remove #{file_name} because process (#{pid}) was active"
    else
      FileUtils.rm_f(pid_path)
      puts "Removed #{file_name} because process was not active"
    end
  end
end

Public Instance Methods

check_concurrency() click to toggle source
# File lib/procodile/cli.rb, line 283
        def check_concurrency
  if supervisor_running?
    reply = ControlClient.run(@config.sock_path, 'check_concurrency', :reload => @options[:reload])
    if reply['started'].empty? && reply['stopped'].empty?
      puts "Processes are running as configured"
    else
      reply['started'].each do |instance|
        puts "Started".color(32) + " #{instance['description']} (PID: #{instance['pid']})"
      end

      reply['stopped'].each do |instance|
        puts "Stopped".color(31) + " #{instance['description']} (PID: #{instance['pid']})"
      end
    end
  else
    raise Error, "Procodile supervisor isn't running"
  end
end
console() click to toggle source
# File lib/procodile/cli.rb, line 405
        def console
  if cmd = @config.console_command
    exec(cmd)
  else
    raise Error, "No console command has been configured in the Procfile"
  end
end
dispatch(command) click to toggle source
# File lib/procodile/cli.rb, line 38
def dispatch(command)
  if self.class.commands.keys.include?(command.to_sym)
    public_send(command)
  else
    raise Error, "Invalid command '#{command}'"
  end
end
exec(command = nil) click to toggle source
# File lib/procodile/cli.rb, line 369
        def exec(command = nil)
  desired_command = command || ARGV.drop(1).join(' ')

  if prefix = @config.exec_prefix
    desired_command = "#{prefix} #{desired_command}"
  end

  if desired_command.length == 0
    raise Error, "You need to specify a command to run (e.g. procodile run -- rake db:migrate)"
  else
    environment = @config.environment_variables

    unless ENV['PROCODILE_EXEC_QUIET'].to_i == 1
      puts "Running with #{desired_command.color(33)}"
      for key, value in environment
        puts "             #{key.color(34)} #{value}"
      end
    end

    begin
      Dir.chdir(@config.root)
      Rbenv.without do
        Kernel.exec(environment, desired_command)
      end
    rescue Errno::ENOENT => e
      raise Error, e.message
    end
  end
end
Also aliased as: run
help() click to toggle source
# File lib/procodile/cli.rb, line 51
        def help
  puts "\e[45;37mWelcome to Procodile v#{Procodile::VERSION}\e[0m"
  puts "For documentation see https://adam.ac/procodile."
  puts

  puts "The following commands are supported:"
  puts
  self.class.commands.sort_by { |k,v| k.to_s }.each do |method, options|
    if options[:description]
      puts "  \e[34m#{method.to_s.ljust(18, ' ')}\e[0m #{options[:description]}"
    end
  end
  puts
  puts "For details for the options available for each command, use the --help option."
  puts "For example 'procodile start --help'."
end
kill() click to toggle source
# File lib/procodile/cli.rb, line 352
        def kill
  Dir[File.join(@config.pid_root, '*.pid')].each do |pid_path|
    name = pid_path.split('/').last.gsub(/\.pid\z/, '')
    pid = File.read(pid_path).to_i
    begin
      ::Process.kill('KILL', pid)
      puts "Sent KILL to #{pid} (#{name})"
    rescue Errno::ESRCH
    end
    FileUtils.rm(pid_path)
  end
end
log() click to toggle source
# File lib/procodile/cli.rb, line 431
        def log
  opts = []
  opts << "-f" if options[:wait]
  opts << "-n #{options[:lines]}" if options[:lines]

  if options[:process]
    if process = @config.processes[options[:process]]
      log_path = process.log_path
    else
      raise Error, "Invalid process name '#{options[:process]}'"
    end
  else
    log_path = @config.log_path
  end
  if File.exist?(log_path)
    exec("tail #{opts.join(' ')} #{log_path}")
  else
    raise Error, "No file found at #{log_path}"
  end
end
reload() click to toggle source
# File lib/procodile/cli.rb, line 264
        def reload
  if supervisor_running?
    ControlClient.run(@config.sock_path, 'reload_config')
    puts "Reloaded Procodile config"
  else
    raise Error, "Procodile supervisor isn't running"
  end
end
restart() click to toggle source
# File lib/procodile/cli.rb, line 233
        def restart
  if supervisor_running?
    instances = ControlClient.run(@config.sock_path, 'restart', :processes => process_names_from_cli_option, :tag => @options[:tag])
    if instances.empty?
      puts "There are no processes to restart."
    else
      instances.each do |old_instance, new_instance|
        if old_instance && new_instance
          if old_instance['description'] == new_instance['description']
            puts "Restarted".color(35) + " #{old_instance['description']}"
          else
            puts "Restarted".color(35) + " #{old_instance['description']} -> #{new_instance['description']}"
          end
        elsif old_instance
          puts "Stopped".color(31) + " #{old_instance['description']}"
        elsif new_instance
          puts "Started".color(32) + " #{new_instance['description']}"
        end
        $stdout.flush
      end
    end
  else
    raise Error, "Procodile supervisor isn't running"
  end
end
run(command = nil)
Alias for: exec
start() click to toggle source
# File lib/procodile/cli.rb, line 125
        def start
  if supervisor_running?
    if @options[:foreground]
      raise Error, "Cannot be started in the foreground because supervisor already running"
    end

    if @options.has_key?(:respawn)
      raise Error, "Cannot disable respawning because supervisor is already running"
    end

    if @options[:stop_when_none]
      raise Error, "Cannot stop supervisor when none running because supervisor is already running"
    end

    if @options[:proxy]
      raise Error, "Cannot enable the proxy when the supervisor is running"
    end

    instances = ControlClient.run(@config.sock_path, 'start_processes', :processes => process_names_from_cli_option, :tag => @options[:tag], :port_allocations => @options[:port_allocations])
    if instances.empty?
      puts "No processes to start."
    else
      instances.each do |instance|
        puts "Started".color(32) + " #{instance['description']} (PID: #{instance['pid']})"
      end
    end
    return
  else
    # The supervisor isn't actually running. We need to start it before processes can be
    # begin being processed
    if @options[:start_supervisor] == false
      raise Error, "Supervisor is not running and cannot be started because --no-supervisor is set"
    else
      self.class.start_supervisor(@config, @options) do |supervisor|
        unless @options[:start_processes] == false
          supervisor.start_processes(process_names_from_cli_option, :tag => @options[:tag])
        end
      end
    end
  end
end
status() click to toggle source
# File lib/procodile/cli.rb, line 320
        def status
  if supervisor_running?
    status = ControlClient.run(@config.sock_path, 'status')
    if @options[:json]
      puts status.to_json
    elsif @options[:json_pretty]
      puts JSON.pretty_generate(status)
    elsif @options[:simple]
      if status['messages'].empty?
        message = status['instances'].map { |p,i| "#{p}[#{i.size}]" }
        puts "OK || #{message.join(', ')}"
      else
        message = status['messages'].map { |p| Message.parse(p) }.join(', ')
        puts "Issues || #{message}"
      end
    else
      require 'procodile/status_cli_output'
      StatusCLIOutput.new(status).print_all
    end
  else
    if @options[:simple]
      puts "NotRunning || Procodile supervisor isn't running"
    else
      raise Error, "Procodile supervisor isn't running"
    end
  end
end
stop() click to toggle source
# File lib/procodile/cli.rb, line 186
        def stop
  if supervisor_running?
    options = {}
    instances = ControlClient.run(@config.sock_path, 'stop', :processes => process_names_from_cli_option, :stop_supervisor => @options[:stop_supervisor])
    if instances.empty?
      puts "No processes were stopped."
    else
      instances.each do |instance|
        puts "Stopped".color(31) + " #{instance['description']} (PID: #{instance['pid']})"
      end
    end

    if @options[:stop_supervisor]
      puts "Supervisor will be stopped when processes are stopped."
    end

    if @options[:wait_until_supervisor_stopped]
      puts "Waiting for supervisor to stop..."
      loop do
        sleep 1
        if supervisor_running?
          sleep 1
        else
          puts "Supervisor has stopped"
          exit 0
        end
      end
    end
  else
    raise Error, "Procodile supervisor isn't running"
  end
end

Private Instance Methods

current_pid() click to toggle source
# File lib/procodile/cli.rb, line 463
def current_pid
  if File.exist?(@config.supervisor_pid_path)
    pid_file = File.read(@config.supervisor_pid_path).strip
    pid_file.length > 0 ? pid_file.to_i : nil
  else
    nil
  end
end
process_names_from_cli_option() click to toggle source
# File lib/procodile/cli.rb, line 478
def process_names_from_cli_option
  if @options[:processes]
    processes = @options[:processes].split(',')
    if processes.empty?
      raise Error, "No process names provided"
    end
    #processes.each do |process|
    #  process_name, _ = process.split('.', 2)
    #  unless @config.process_list.keys.include?(process_name.to_s)
    #    raise Error, "Process '#{process_name}' is not configured. You may need to reload your config."
    #  end
    #end
    processes
  else
    nil
  end
end
supervisor_running?() click to toggle source
# File lib/procodile/cli.rb, line 455
def supervisor_running?
  if pid = current_pid
    self.class.pid_active?(pid)
  else
    false
  end
end