2018-09-17 12:41:14 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2011-12-05 18:58:23 -05:00
|
|
|
require 'optparse'
|
2018-08-21 21:10:02 -04:00
|
|
|
require_relative 'state_file'
|
|
|
|
require_relative 'const'
|
|
|
|
require_relative 'detect'
|
|
|
|
require_relative 'configuration'
|
2011-12-05 18:58:23 -05:00
|
|
|
require 'uri'
|
|
|
|
require 'socket'
|
2016-02-21 00:01:33 -05:00
|
|
|
|
2011-12-05 18:58:23 -05:00
|
|
|
module Puma
|
|
|
|
class ControlCLI
|
|
|
|
|
2020-10-28 09:09:37 -04:00
|
|
|
# values must be string or nil
|
|
|
|
# value of `nil` means command cannot be processed via signal
|
|
|
|
# @version 5.0.3
|
|
|
|
CMD_PATH_SIG_MAP = {
|
|
|
|
'gc' => nil,
|
|
|
|
'gc-stats' => nil,
|
|
|
|
'halt' => 'SIGQUIT',
|
|
|
|
'phased-restart' => 'SIGUSR1',
|
|
|
|
'refork' => 'SIGURG',
|
|
|
|
'reload-worker-directory' => nil,
|
|
|
|
'restart' => 'SIGUSR2',
|
|
|
|
'start' => nil,
|
|
|
|
'stats' => nil,
|
|
|
|
'status' => '',
|
|
|
|
'stop' => 'SIGTERM',
|
|
|
|
'thread-backtraces' => nil
|
|
|
|
}.freeze
|
|
|
|
|
|
|
|
# @deprecated 6.0.0
|
|
|
|
COMMANDS = CMD_PATH_SIG_MAP.keys.freeze
|
|
|
|
|
|
|
|
# commands that cannot be used in a request
|
|
|
|
NO_REQ_COMMANDS = %w{refork}.freeze
|
2020-09-17 11:15:19 -04:00
|
|
|
|
|
|
|
# @version 5.0.0
|
2020-10-28 09:09:37 -04:00
|
|
|
PRINTABLE_COMMANDS = %w{gc-stats stats thread-backtraces}.freeze
|
2012-09-02 08:56:05 -04:00
|
|
|
|
2012-09-25 12:40:00 -04:00
|
|
|
def initialize(argv, stdout=STDOUT, stderr=STDERR)
|
2016-02-06 22:00:29 -05:00
|
|
|
@state = nil
|
|
|
|
@quiet = false
|
|
|
|
@pidfile = nil
|
|
|
|
@pid = nil
|
|
|
|
@control_url = nil
|
|
|
|
@control_auth_token = nil
|
|
|
|
@config_file = nil
|
|
|
|
@command = nil
|
2019-11-11 00:10:02 -05:00
|
|
|
@environment = ENV['RACK_ENV'] || ENV['RAILS_ENV']
|
2016-02-06 22:00:29 -05:00
|
|
|
|
|
|
|
@argv = argv.dup
|
2011-12-07 17:48:41 -05:00
|
|
|
@stdout = stdout
|
2012-09-25 12:40:00 -04:00
|
|
|
@stderr = stderr
|
2015-08-15 13:05:59 -04:00
|
|
|
@cli_options = {}
|
2013-08-01 06:04:44 -04:00
|
|
|
|
2012-10-16 01:24:22 -04:00
|
|
|
opts = OptionParser.new do |o|
|
2020-10-28 09:09:37 -04:00
|
|
|
o.banner = "Usage: pumactl (-p PID | -P pidfile | -S status_file | -C url -T token | -F config.rb) (#{CMD_PATH_SIG_MAP.keys.join("|")})"
|
2012-10-16 01:24:22 -04:00
|
|
|
|
|
|
|
o.on "-S", "--state PATH", "Where the state file to use is" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@state = arg
|
2012-09-02 08:56:05 -04:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
|
|
|
o.on "-Q", "--quiet", "Not display messages" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@quiet = true
|
2012-09-02 08:56:05 -04:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
|
|
|
o.on "-P", "--pidfile PATH", "Pid file" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@pidfile = arg
|
2012-09-02 08:56:05 -04:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2012-10-16 01:41:23 -04:00
|
|
|
o.on "-p", "--pid PID", "Pid" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@pid = arg.to_i
|
2012-10-16 01:41:23 -04:00
|
|
|
end
|
|
|
|
|
2012-10-16 01:24:22 -04:00
|
|
|
o.on "-C", "--control-url URL", "The bind url to use for the control server" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@control_url = arg
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
|
|
|
o.on "-T", "--control-token TOKEN", "The token to use as authentication for the control server" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@control_auth_token = arg
|
2012-09-02 08:56:05 -04:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2013-07-19 02:30:15 -04:00
|
|
|
o.on "-F", "--config-file PATH", "Puma config script" do |arg|
|
2016-02-06 22:00:29 -05:00
|
|
|
@config_file = arg
|
2013-07-19 02:30:15 -04:00
|
|
|
end
|
|
|
|
|
2019-08-10 14:34:35 -04:00
|
|
|
o.on "-e", "--environment ENVIRONMENT",
|
|
|
|
"The environment to run the Rack app on (default development)" do |arg|
|
|
|
|
@environment = arg
|
|
|
|
end
|
|
|
|
|
2012-10-16 01:24:22 -04:00
|
|
|
o.on_tail("-H", "--help", "Show this message") do
|
2012-10-16 01:37:28 -04:00
|
|
|
@stdout.puts o
|
2012-09-02 08:56:05 -04:00
|
|
|
exit
|
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
|
|
|
o.on_tail("-V", "--version", "Show version") do
|
2020-11-17 09:30:08 -05:00
|
|
|
@stdout.puts Const::PUMA_VERSION
|
2012-09-02 08:56:05 -04:00
|
|
|
exit
|
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
end
|
|
|
|
|
2012-10-16 01:37:28 -04:00
|
|
|
opts.order!(argv) { |a| opts.terminate a }
|
Parse command-line options for pumactl (#1482)
* Parse command-line options for pumactl
Before, we would remove any non-yielding
args, but missed to actually parse them.
Resulting in issues like, --pidfile or
similar args not taking any effect.
* Remove misleading message of puma starting on pumactl exit
This way, when puma shuts down (running from pumactl), it does not end
with "Puma is started" message. Because, the copy is dependent
on the value of `@command`, the same will always point
to `start`, if pumactl was issued a `start`, hence during
exit, this message appeared as misleading.
2018-03-19 16:51:45 -04:00
|
|
|
opts.parse!
|
2013-08-01 06:04:44 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
@command = argv.shift
|
2015-08-15 13:05:59 -04:00
|
|
|
|
2020-05-06 03:15:05 -04:00
|
|
|
# check presence of command
|
|
|
|
unless @command
|
2020-10-28 09:09:37 -04:00
|
|
|
raise "Available commands: #{CMD_PATH_SIG_MAP.keys.join(", ")}"
|
2020-05-06 03:15:05 -04:00
|
|
|
end
|
|
|
|
|
2020-10-28 09:09:37 -04:00
|
|
|
unless CMD_PATH_SIG_MAP.key? @command
|
2020-05-06 03:15:05 -04:00
|
|
|
raise "Invalid command: #{@command}"
|
|
|
|
end
|
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
unless @config_file == '-'
|
2019-10-18 01:49:05 -04:00
|
|
|
environment = @environment || 'development'
|
|
|
|
|
2019-08-10 14:34:35 -04:00
|
|
|
if @config_file.nil?
|
2019-10-18 01:49:05 -04:00
|
|
|
@config_file = %W(config/puma/#{environment}.rb config/puma.rb).find do |f|
|
2019-08-10 14:34:35 -04:00
|
|
|
File.exist?(f)
|
|
|
|
end
|
2015-06-11 09:13:33 -04:00
|
|
|
end
|
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
if @config_file
|
2017-03-10 11:30:27 -05:00
|
|
|
config = Puma::Configuration.new({ config_files: [@config_file] }, {})
|
|
|
|
config.load
|
|
|
|
@state ||= config.options[:state]
|
|
|
|
@control_url ||= config.options[:control_url]
|
2016-02-06 22:00:29 -05:00
|
|
|
@control_auth_token ||= config.options[:control_auth_token]
|
2017-03-10 11:30:27 -05:00
|
|
|
@pidfile ||= config.options[:pidfile]
|
2015-06-11 09:13:33 -04:00
|
|
|
end
|
|
|
|
end
|
2012-09-02 08:56:05 -04:00
|
|
|
rescue => e
|
|
|
|
@stdout.puts e.message
|
|
|
|
exit 1
|
|
|
|
end
|
2013-08-01 06:04:44 -04:00
|
|
|
|
2012-10-16 01:24:22 -04:00
|
|
|
def message(msg)
|
2016-02-06 22:00:29 -05:00
|
|
|
@stdout.puts msg unless @quiet
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
def prepare_configuration
|
2016-02-06 22:00:29 -05:00
|
|
|
if @state
|
|
|
|
unless File.exist? @state
|
|
|
|
raise "State file not found: #{@state}"
|
2012-10-16 01:24:22 -04:00
|
|
|
end
|
|
|
|
|
2016-02-26 01:16:07 -05:00
|
|
|
sf = Puma::StateFile.new
|
2016-02-06 22:00:29 -05:00
|
|
|
sf.load @state
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
@control_url = sf.control_url
|
|
|
|
@control_auth_token = sf.control_auth_token
|
|
|
|
@pid = sf.pid
|
|
|
|
elsif @pidfile
|
2012-09-02 08:56:05 -04:00
|
|
|
# get pid from pid_file
|
2020-11-27 10:47:33 -05:00
|
|
|
@pid = File.read(@pidfile, mode: 'rb:UTF-8').to_i
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
def send_request
|
2016-02-06 22:00:29 -05:00
|
|
|
uri = URI.parse @control_url
|
2013-08-01 06:04:44 -04:00
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
# create server object by scheme
|
2020-10-28 09:09:37 -04:00
|
|
|
server =
|
|
|
|
case uri.scheme
|
|
|
|
when 'ssl'
|
|
|
|
require 'openssl'
|
|
|
|
OpenSSL::SSL::SSLSocket.new(
|
|
|
|
TCPSocket.new(uri.host, uri.port),
|
|
|
|
OpenSSL::SSL::SSLContext.new)
|
|
|
|
.tap { |ssl| ssl.sync_close = true } # default is false
|
|
|
|
.tap(&:connect)
|
|
|
|
when 'tcp'
|
|
|
|
TCPSocket.new uri.host, uri.port
|
|
|
|
when 'unix'
|
|
|
|
UNIXSocket.new "#{uri.host}#{uri.path}"
|
|
|
|
else
|
|
|
|
raise "Invalid scheme: #{uri.scheme}"
|
|
|
|
end
|
|
|
|
|
|
|
|
if @command == 'status'
|
|
|
|
message 'Puma is started'
|
|
|
|
elsif NO_REQ_COMMANDS.include? @command
|
|
|
|
raise "Invalid request command: #{@command}"
|
2012-10-16 01:24:22 -04:00
|
|
|
else
|
2016-02-06 22:00:29 -05:00
|
|
|
url = "/#{@command}"
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
if @control_auth_token
|
|
|
|
url = url + "?token=#{@control_auth_token}"
|
2012-09-02 08:56:05 -04:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2020-10-28 09:09:37 -04:00
|
|
|
server.syswrite "GET #{url} HTTP/1.0\r\n\r\n"
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2018-08-21 21:10:02 -04:00
|
|
|
unless data = server.read
|
2020-10-28 09:09:37 -04:00
|
|
|
raise 'Server closed connection before responding'
|
2013-06-01 17:24:33 -04:00
|
|
|
end
|
2013-06-01 16:55:17 -04:00
|
|
|
|
|
|
|
response = data.split("\r\n")
|
|
|
|
|
|
|
|
if response.empty?
|
|
|
|
raise "Server sent empty response"
|
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2020-10-28 09:09:37 -04:00
|
|
|
@http, @code, @message = response.first.split(' ',3)
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2020-10-28 09:09:37 -04:00
|
|
|
if @code == '403'
|
|
|
|
raise 'Unauthorized access to server (wrong auth token)'
|
|
|
|
elsif @code == '404'
|
2013-07-05 19:08:13 -04:00
|
|
|
raise "Command error: #{response.last}"
|
2020-10-28 09:09:37 -04:00
|
|
|
elsif @code != '200'
|
2012-09-02 08:56:05 -04:00
|
|
|
raise "Bad response from server: #{@code}"
|
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
message "Command #{@command} sent success"
|
2019-11-11 00:08:41 -05:00
|
|
|
message response.last if PRINTABLE_COMMANDS.include?(@command)
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
2018-08-21 21:10:02 -04:00
|
|
|
ensure
|
2020-03-30 19:22:10 -04:00
|
|
|
if server
|
2020-10-28 09:09:37 -04:00
|
|
|
if uri.scheme == 'ssl'
|
2020-03-30 19:22:10 -04:00
|
|
|
server.sysclose
|
|
|
|
else
|
|
|
|
server.close unless server.closed?
|
|
|
|
end
|
|
|
|
end
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
def send_signal
|
2016-02-06 22:00:29 -05:00
|
|
|
unless @pid
|
2020-10-28 09:09:37 -04:00
|
|
|
raise 'Neither pid nor control url available'
|
2012-10-16 01:41:23 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
begin
|
2020-10-28 09:09:37 -04:00
|
|
|
sig = CMD_PATH_SIG_MAP[@command]
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2020-10-28 09:09:37 -04:00
|
|
|
if sig.nil?
|
2020-11-17 09:30:08 -05:00
|
|
|
@stdout.puts "'#{@command}' not available via pid only"
|
2020-12-02 12:43:53 -05:00
|
|
|
@stdout.flush unless @stdout.sync
|
2016-12-12 14:56:15 -05:00
|
|
|
return
|
2020-10-28 09:09:37 -04:00
|
|
|
elsif sig.start_with? 'SIG'
|
|
|
|
Process.kill sig, @pid
|
|
|
|
elsif @command == 'status'
|
2019-06-23 20:36:06 -04:00
|
|
|
begin
|
|
|
|
Process.kill 0, @pid
|
2020-11-17 09:30:08 -05:00
|
|
|
@stdout.puts 'Puma is started'
|
2020-12-02 12:43:53 -05:00
|
|
|
@stdout.flush unless @stdout.sync
|
2019-06-23 20:36:06 -04:00
|
|
|
rescue Errno::ESRCH
|
2020-10-28 09:09:37 -04:00
|
|
|
raise 'Puma is not running'
|
2019-06-23 20:36:06 -04:00
|
|
|
end
|
2016-12-12 14:56:15 -05:00
|
|
|
return
|
|
|
|
end
|
|
|
|
rescue SystemCallError
|
2020-10-28 09:09:37 -04:00
|
|
|
if @command == 'restart'
|
2016-12-12 14:56:15 -05:00
|
|
|
start
|
|
|
|
else
|
|
|
|
raise "No pid '#{@pid}' found"
|
|
|
|
end
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
2012-10-16 01:24:22 -04:00
|
|
|
|
2016-02-06 22:00:29 -05:00
|
|
|
message "Command #{@command} sent success"
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
def run
|
2020-10-28 09:09:37 -04:00
|
|
|
return start if @command == 'start'
|
2012-09-02 08:56:05 -04:00
|
|
|
prepare_configuration
|
2013-08-01 06:04:44 -04:00
|
|
|
|
2020-10-28 09:09:37 -04:00
|
|
|
if Puma.windows? || @control_url
|
2012-09-02 08:56:05 -04:00
|
|
|
send_request
|
2011-12-05 18:58:23 -05:00
|
|
|
else
|
2020-10-28 09:09:37 -04:00
|
|
|
send_signal
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
|
|
|
|
2012-09-02 08:56:05 -04:00
|
|
|
rescue => e
|
|
|
|
message e.message
|
|
|
|
exit 1
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
2013-08-01 06:04:44 -04:00
|
|
|
|
2020-03-07 09:15:43 -05:00
|
|
|
private
|
2013-08-01 06:04:44 -04:00
|
|
|
def start
|
|
|
|
require 'puma/cli'
|
|
|
|
|
2016-03-05 18:57:24 -05:00
|
|
|
run_args = []
|
|
|
|
|
|
|
|
run_args += ["-S", @state] if @state
|
|
|
|
run_args += ["-q"] if @quiet
|
|
|
|
run_args += ["--pidfile", @pidfile] if @pidfile
|
2017-09-22 16:41:34 -04:00
|
|
|
run_args += ["--control-url", @control_url] if @control_url
|
2016-03-05 18:57:24 -05:00
|
|
|
run_args += ["--control-token", @control_auth_token] if @control_auth_token
|
|
|
|
run_args += ["-C", @config_file] if @config_file
|
2019-08-10 14:34:35 -04:00
|
|
|
run_args += ["-e", @environment] if @environment
|
2013-08-08 19:10:26 -04:00
|
|
|
|
2013-08-01 06:04:44 -04:00
|
|
|
events = Puma::Events.new @stdout, @stderr
|
|
|
|
|
2015-10-28 08:18:28 -04:00
|
|
|
# replace $0 because puma use it to generate restart command
|
2015-11-06 14:00:49 -05:00
|
|
|
puma_cmd = $0.gsub(/pumactl$/, 'puma')
|
2015-10-28 08:18:28 -04:00
|
|
|
$0 = puma_cmd if File.exist?(puma_cmd)
|
|
|
|
|
2013-08-01 06:04:44 -04:00
|
|
|
cli = Puma::CLI.new run_args, events
|
|
|
|
cli.run
|
|
|
|
end
|
2011-12-05 18:58:23 -05:00
|
|
|
end
|
2013-08-01 06:04:44 -04:00
|
|
|
end
|